[Tutorial] Scripting Libraries

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 versionsyou 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 LibrariesScript 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 libraryThe 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:

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:

Applescript:

use myLib : script"Basic Text Utilities"

myLib's textReplace("Hxllo World!", "x", "a")

or, using a tell block

Applescript:

Applescript:

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).

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.

Applescript:

Applescript:

Applescript:

script"ASOC Text Utilities"'s textReplace("Hxllo World!", "x", "a")

Script Library with our own terminologyBy 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.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"><dictionary><suite name="Text Suite" code="TeSu" description="Text Suite contains a series of commands that can be used to change or get information about a string object. Read the command's description for more information about each command"> </suite></dictionary>

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".

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"><dictionary> <suite name="Text Suite" code="TeSu" description="Text Suite contains a series of commands that can be used to change or get information about a string object. Read the command's description for more information about each command"><command name="replace in string" code="TeSuStRe" description="Search and replace occurrences of a substring in a string"> </command> </suite></dictionary>

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.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"><dictionary> <suite name="Text Suite" code="TeSu" description="Text Suite contains a series of commands that can be used to change or get information about a string object. Read the command's description for more information about each command"> <command name="replace in string" code="TeSuStRe" description="Search and replace occurences of a substring in a string"><direct-parameter description="The text that needs substring to be replaced" type="text"/> </command> </suite></dictionary>

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.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"><dictionary> <suite name="Text Suite" code="TeSu" description="Text Suite contains a series of commands that can be used to change or get information about a string object. Read the command's description for more information about each command"> <command name="replace in string" code="TeSuStRe" description="Search and replace occurences of a substring in a string"> <direct-parameter description="The text that needs substring to be replaced" type="text"/><parameter name="search for" code="SeFo" type="text" description="String the that needs to be removed" /> <parameter name="replace with" code="ReWi" type="text" description="String that needs to be placed" /> <parameter name="using search option" code="SeOp" type="search option" description="Type of search that's been used" /> </command> </suite></dictionary>

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:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"><dictionary> <suite name="Text Suite" code="TeSu" description="Text Suite contains a series of commands that can be used to change or get information about a string object. Read the command's description for more information about each command"> <command name="replace in string" code="TeSuStRe" description="Search and replace occurences of a substring in a string"> <direct-parameter description="The text that needs substring to be replaced" type="text"/> <parameter name="search for" code="SeFo" type="text" description="String the that needs to be removed" /> <parameter name="replace with" code="ReWi" type="text" description="String that needs to be placed" /> <parameter name="using search option" code="SeOp" type="search option" description="Type of search that's been used" /> </command><enumeration name="search option" code="sEoP" description="Kind of search you want to use"> </enumeration> </suite></dictionary>

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.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"><dictionary> <suite name="Text Suite" code="TeSu" description="Text Suite contains a series of commands that can be used to change or get information about a string object. Read the command's description for more information about each command"> <command name="replace in string" code="TeSuStRe" description="Search and replace occurences of a substring in a string"> <direct-parameter description="The text that needs substring to be replaced" type="text"/> <parameter name="search for" code="SeFo" type="text" description="String the that needs to be removed" /> <parameter name="replace with" code="ReWi" type="text" description="String that needs to be placed" /> <parameter name="using search option" code="SeOp" type="search option" description="Type of search that's been used" /> </command> <enumeration name="search option" code="sEoP" description="Kind of search you want to use"><enumerator name="text search" code="tEsE" description="A text search considering case and composing of characters" /> <enumerator name="binary search" code="bIsE" description="Search byte for byte, case and composing of characters are ignored" /> </enumeration> </suite></dictionary>

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:

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.

Applescript:

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 librariesScript 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 usedIn 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 sdefFor 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 McUsrrevision 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.

Re: [Tutorial] Scripting Libraries

Hello.

I had a slight problem with compiling the code, which I pinned down to being having Script Debugger 4.5 set up as the default editor. Not so understandable, as it is pretty pre Applescript 2.6.

Quitting the editors after having set AppleScript Editor to the default AppleScript Editor, and then restarting both of them solved all issues, at least as long as I compile the new code in AppleScript Editor.

Re: [Tutorial] Scripting Libraries

Having read this excellent description rather carefully, though I haven't thoroughly tested it yet, I have decided to include it with our other tutorials in unScripted. Well done.

McUsrII's comment that this tutorial must be tested in the Script Editor rather than in Script Debugger arises from the the differences in way SE handles dictionaries compared to SD. I have no doubt that a fix for SD will make this right eventually.

Re: [Tutorial] Scripting Libraries

Hello.

I want to add that ScriptLibraries included by the new "use" keyword, that are without an sdef file, works flawlessly with my ScriptDebugger 4.5.7 that I bought originally for Snow Leopard. -Now, that is value for money! Thank you Late Night Software!

I already employ error controls in the script libraries handler to catch invalid classes being used as arguments. But they are inside the handler. I cannot find a way to catch an error in the parameters though.

Re: [Tutorial] Scripting Libraries

DJ Bazzie Wazzie (me) wrote:

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.

While writing this tutorial I've tested the optional parameter and found out that you can add this attribute to the parameter element but it's value is ignored. As for applications and scripting additions the binding process is completely different and therefore optional values can be used. The most important reason is script libraries, using it's own terminology or not, is an instantiated script object and not an C library that's dynamically loaded directly into the AppleScript Interpreter. That means, like Shane also said, it's limited to the rules of AppleScript objects and handlers.

Because script libraries are instantiated AppleScript objects the optional parameters should be made in the AppleScript language. I like the idea of optional parameters but how would that work since AppleScript handlers supports both labeled parameters and positional parameters? If you have an handler for uppercasing you would have something like:

Applescript:

on makeUpper(theString, mode:1) --optional without own terminologyend makeUpper

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.

Re: [Tutorial] Scripting Libraries

DJ Bazzie Wazzie wrote:

@Mark Hunte:

Dj Bazzie Wazzie wrote:

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.

While that's true, there's an important exception: if you are using the same term as Apple, you should use the same code. Mark's example uses the code standard additions uses for the replacing parameter (in store script).

Re: [Tutorial] Scripting Libraries

Shane Stanley wrote:

DJ Bazzie Wazzie wrote:

@Mark Hunte:

Dj Bazzie Wazzie wrote:

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.

While that's true, there's an important exception: if you are using the same term as Apple, you should use the same code. Mark's example uses the code standard additions uses for the replacing parameter (in store script).

I thought so to until I watched video 416 of the wwdc (Introducing AppleScript Libraries). There it say "The use of codes, comprised of all lower case letter, is reserved by Apple." and as "Sal Soghoian" says that you need to use at least 1 upper case character in your code, not saying anything about re-using codes by Apple. Am I missing something here, please tell me then I can change that in my tutorial above as well.

Re: [Tutorial] Scripting Libraries

Thanks for the prompt reply Shane.

With regards to the codes reserved by Apple, the same exception as Shane stated above pertains to those. Just writing this for completion. There is a long list of the codes for the return types in: Cocoa Scripting Guide table 1-1.

Re: [Tutorial] Scripting Libraries

Not as sexy, but it's possible to have pseudo-optional parameters in AppleScript by passing them in a list or a record. You still have to pass something (ie. the list or the record), but it doesn't have to be fully populated:

Applescript:

on addUp(aList)set |sum| to 0repeatwith thisValue in aListset |sum| to |sum| + thisValueendrepeat

Re: [Tutorial] Scripting Libraries

Shane Stanley wrote:

DJ Bazzie Wazzie wrote:

@Mark Hunte:

Dj Bazzie Wazzie wrote:

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.

While that's true, there's an important exception: if you are using the same term as Apple, you should use the same code. Mark's example uses the code standard additions uses for the replacing parameter (in store script).

Thanks.I Know about the code thing and my own codes comply. As Shane say I used apple code here. But only because I was doing a test. Not to be used in my real code.

Re: [Tutorial] Scripting Libraries

DJ Bazzie Wazzie wrote:

I thought so to until I watched video 416 of the wwdc (Introducing AppleScript Libraries). There it say "The use of codes, comprised of all lower case letter, is reserved by Apple." and as "Sal Soghoian" says that you need to use at least 1 upper case character in your code, not saying anything about re-using codes by Apple. Am I missing something here, please tell me then I can change that in my tutorial above as well.

I think Sal was trying to simplify what is actually a fairly tricky business. The commands themselves should be unique both in the term used and the code. But for parameters, and enumerations, the rules are different. For example, if you are going to have an enumeration with yes/no/ask enumerators, using an exact copy of the one used in standard additions is perfectly fine, and makes sense in terms of the pool of available codes -- just don't mess with it. Importantly, it pretty much guarantees that you will not conflict with another definition -- and avoiding conflicts is what the guidelines are all about. The same goes for parameters, although it is not compulsory that they be the same.

Apple still have some defined suites, and although they haven't been updated for a long time, the whole idea behind them was that developers would use consistent terms and codes.

From tech note TN2106:

If you use the same term in more than one place in your dictionary, all of them must use the same 4-byte code. For example, if you use input as a parameter, again as a property, and later as an enumerator, use the same type code in all three places. Conversely, if you use the same type code in more than one place, all of them must use the same term. This also applies to terms and codes inherited from system- or framework-supplied dictionaries. For example, if you define a color property for a class, its code must be 'colr', because that is how it is defined in the AppleScript dictionary and Cocoa's NSCoreSuite script suite. Failure to follow this rule will cause your scripts to fail or change in odd ways when they are compiled or decompiled â€“ AppleScript will change the original term to be the last one defined.

The quote was aimed at app developers under the pre-use regime. With the use command, the terminology is being pooled, so you can't just rely on your terms trumping scripting addition terms.

Three things:

The use command, and especially the ability to avoid scripting additions, gives us control over how conflicts are handled, so we possibly don't have to be so rigid about it all. The fact that conflicts are called out at compile time is a good thing. But it doesn't hurt to try to avoid them in the first place, to avoid the extra work of including extra tell or using terms from blocks.

The best way to limit conflict with the command part is to try to use multi-word terms. Scripting additions with very large dictionaries can be a serious cause of conflict. (If third-party scripting additions disappeared, this would all be a lot simpler.)

Finally, the all-lowercase rule of thumb is pretty rough. There are quite a few case where Apple has used other characters. (Most of the parameters to the say command, for instance, are all-uppercase.)

(And in case you are wondering: if you write your dictionary using ASObjC Explorer, the red colouring that comes up when you enter a code that conflicts is based on my building a list from trawling through Apple's headers, so it should be reasonably accurate. But it just means you should not use it for a command, or unless you also use the same name.)

Re: [Tutorial] Scripting Libraries

Thanks Shane for the thorough answer.

I was expecting the use of parameters as you said and I had it written down in as you replied in my first edition of this tutorial. I was aware what the documentations says about code usage while making scripting additions and applications scriptable. But after watching the video I wasn't sure, so I chose for a better-safe-than-sorry way. The sdef is extending the terminology like scripting additions but it's still just an AppleScript object. And besides that I couldn't really find anything that says explicitly that it needs to follow the guidelines for scriptability in applications or scripting additions.

Re: [Tutorial] Scripting Libraries

POSIX File is part of the standard scripting addition. When you use the "use script" statement you should use the line "use scripting additions" as well if you want to make use of scripting additions. When you use the "use script" statement, scripting additions are by default not included into your script. It's something that I should have mentioned in my tutorial I guess.

Re: [Tutorial] Scripting Libraries

DJ Bazzie Wazzie wrote:

POSIX File is part of the standard scripting addition. When you use the "use script" statement you should use the line "use scripting additions" as well if you want to make use of scripting additions. When you use the "use script" statement, scripting additions are by default not included into your script. It's something that I should have mentioned in my tutorial I guess.

Thanks.

Annoyingly that was the first thing I tried. But clearly did not have enough coffee this morning because I now realise I must have used