You’re looking at a draft of a chapter from a work in progress, tentatively titled Scripting Mac Applications With Ruby: An AppleScript Alternative, by Matt Neuburg.
Covers rb-appscript 0.6.1. Last revised Jun 23, 2012. All content ©2012 by the author, all rights reserved.
Chapter 8: The Dictionary
1. How the Dictionary is Formatted
2. How rb-appscript Accesses the Dictionary
3. Getting a Look at the Dictionary
3.1. Script Debugger and Script Editor
3.2. ASDictionary
3.3. Using ASDictionary Within Ruby
4.1. Suites
4.2. Comments
4.3. Classes
4.4. Properties
4.5. Elements
4.6. Commands
4.7. Expected class_
Type
4.8. Enumerations
5.1. Coercions
5.2. Shortcuts
5.3. Plurals
5.4. Missing Facts
5.5. Expected Types
5.6. Nonsense Allowed
5.7. Tools and Techniques
As explained earlier (see Chapter 3), every scriptable application, if it is to be scriptable with AppleScript, has a resource called the dictionary. This resource is machine-parsable, and is how rb-appscript is able to populate Ruby objects with English-like methods whose names can be translated into the four-letter codes that constitute Apple events. For example, when you say:
itu = Appscript.app("iTunes.app")
itu.play(itu.playlists["blues"].tracks[1], {:once => true})
The terms play
, playlists
, tracks
, and :once
come from the iTunes dictionary. That’s how rb-appscript knows you can say them to iTunes, and how it transforms that Ruby code into an Apple event that iTunes can understand. If you try to talk to iTunes using terminology that isn’t in the iTunes dictionary, rb-appscript complains, and no Apple event is sent:
itu = Appscript.app("iTunes.app")
itu.weeble #=> RuntimeError: Unknown property, element or command: 'weeble'
You, the human user, can also examine the dictionary to discover its terminology. That’s how you know that play
is a command that iTunes understands, and that weeble
is not. A scriptable application’s dictionary constitutes your primary mode of documentation for discovering what terminology each scriptable application supports. It isn’t always particularly good documentation; it can say things that are false, and it can fail to communicate important information. But it’s still primary. The art of communicating with a scriptable application consists of studying and understanding the dictionary, supplemented by secondary means such as experimentation. This chapter is about how to understand a scriptable application’s dictionary. (The experimentation is left as an exercise for the reader, but Chapter 10 gives an idea of the sort of thinking that’s typically necessary.)
The dictionary is not, in its raw form, human-readable. A scriptable application is not going to show it to you directly; it is embedded, in a non-obvious way, in the application itself. Dictionaries come in two primary formats and a third format that might be termed intermediate:
The 'aete'
resource. Back in the old System 7 days when Apple events were invented, an application’s secondary resources (icons, pictures, everything except its executable code) were kept in its resource fork, whose contents were distinguished by four-letter codes. The dictionary is such a resource, and is distinguished by the four-letter code 'aete'
. The 'aete'
resource is a tightly-packed format, completely incomprehensible except to a machine — remember, space was at a premium in those days, and text-based formats for describing structured data, such as XML, had not yet been invented. This format is typical of Carbon applications. Resource forks are no longer much used, but there are resource files (file extension .rsrc) whose contents have the same structure as a resource fork.
For example, the Finder (up through the time of this writing) is a Carbon application. Resources are contained in its bundle in a file called Finder.rsrc. If you’ve installed the Xcode Developer Tools, you can use DeRez at the command line to see the Finder’s dictionary:
$ DeRez -only aete -useDF /System/Library/CoreServices/Finder.app/Contents/Resources/Finder.rsrc
data 'aete' (0) {
$"0090 0000 0000 0009 0E53 7461 6E64 6172" /* .?.....?.Standar */
$"6420 5375 6974 6532 436F 6D6D 6F6E 2074" /* d Suite2Common t */
$"6572 6D73 2074 6861 7420 6D6F 7374 2061" /* erms that most a */
$"7070 6C69 6361 7469 6F6E 7320 7368 6F75" /* pplications shou */
$"6C64 2073 7570 706F 7274 436F 5265 0001" /* ld supportCoRe.. */
...
You can’t read it (though it does contain some English phrases, as you see), but you can see it’s there.
The 'sdef'
file. An 'sdef'
file is an XML file with a certain structure (to learn about that structure, type man 5 sdef
at the command line). XML is just text, so it’s a lot easier to understand — though it is still is not intended for direct human consumption.
For example, iChat uses an 'sdef'
dictionary, and you can view it directly in the Terminal:
$ cat /Applications/iChat.app/Contents/Resources/iChat.sdef
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<dictionary title="Dictionary">
<suite name="Standard Suite" code="core" description="Common classes and commands for all applications.">
<cocoa name="NSCoreSuite"/>
<enumeration name="save options" code="savo">
<enumerator name="yes" code="yes " description="Save the file."/>
<enumerator name="no" code="no " description="Do not save the file."/>
<enumerator name="ask" code="ask " description="Ask the user whether or not to save the file."/>
...
The scriptSuite
/ scriptTerminology
pair. The 'sdef'
file format didn’t become official until Leopard (Mac OS X 10.5). Prior to that, Cocoa applications used a transitional format, consisting of a pair of property-list files with extensions .scriptSuite and .scriptTerminology. This format is transitional in the sense that it is not actually used directly. Instead, when the application runs, these files are read and translated into an 'aete'
structure.
For example, iPhoto (as of this writing) uses a scriptSuite
/ scriptTerminology
dictionary, and you can view it directly in the Terminal:
$ cat /Applications/iPhoto.app/Contents/Resources/iPhoto.scriptSuite
{
AppleEventCode = iPho;
Classes = {
Album = {
AppleEventCode = ipal;
...
$ cat /Applications/iPhoto.app/Contents/Resources/iPhoto.scriptTerminology
{
Classes = {
Album = {
Attributes = {
name = {
Description = "The name of the album.";
Name = "name";
};
In general, you won’t know or care how a dictionary is formatted, but it does make a difference to your experience, in two ways.
First, as you can clearly see from the above examples, I’m not lying when I say that a dictionary is not, of itself, human-readable; there needs to be some sort of mediation to present it nicely and conveniently (that is the topic of the next section).
Second, the dictionary format has a bearing on the fact that some applications must be running in order for their dictionary to be accessed. A dictionary can be static or dynamic. If it’s static, an 'aete'
resource or an 'sdef'
file can be read straight off the disk, without launching the application. But all scriptSuite
/ scriptTerminology
dictionaries are dynamic, and some applications that supply their dictionaries in the other formats supply them dynamically as well (usually because, like BBEdit or InDesign, they can incorporate scriptable plug-ins whose presence is not detected until the application is launched). A dynamic dictionary can only be supplied by the running application, and this explains why so many applications will launch themselves merely because you attempt to access their dictionary.
(Some scriptable applications take a compromise approach: they redundantly include both an 'aete'
resource and a scriptSuite
/ scriptTerminology
pair. The former is used when some other application wants to read the dictionary, precisely so that the scriptable application will not necessarily be launched. The latter is used internally to support the application’s scriptability. iCal is an example.)
In order to construct the list of legal methods that you can send to a particular Appscript::Reference
instance (including an Appscript::Application
instance, since Appscript::Application
is a subclass of Appscript::Reference
), rb-appscript asks a target scriptable application for its dictionary the first time you either send it any message that might involve terminology (other than the built-in commands discussed in Chapter 6). It does this by sending the scriptable application an ascr/gdte
event, which retrieves the dictionary directly from the application. That, in turn, necessitates that the target application be running. This makes sense, even if the application has a static dictionary, since a moment later your own code would presumably proceed to send the application an Apple event anyway.
For example:
itu = Appscript.app("iTunes.app")
itu.activate # dictionary not acquired ("activate" is a built-in command)
Contrast that with this example:
itu = Appscript.app("iTunes.app")
src = itu.sources # dictionary acquired; iTunes launched if not running
Having acquired a scriptable application’s dictionary, rb-appscript caches it for as long as your Ruby script runs. Thus, subsequent use of an Appscript::Application
or Appscript::Reference
referring to the same scriptable application does not access the dictionary a second time.
itu = Appscript.app("iTunes.app")
src = itu.sources[1] # dictionary acquired
src.playlists[1].get # dictionary already cached, not re-acquired
itu2 = Appscript.app("iTunes.app")
src2 = itu.sources[1] # dictionary already cached, not re-acquired
That example shows that, even though itu
and itu2
are two different instances, rb-appscript knows (using the UNIX process ID) that they refer to the very same scriptable application process, and does not needlessly reacquire the dictionary. Even if, in the course of a single Ruby script, you quit a script application and start it up again, rb-appscript might not have to acquire the dictionary again:
itu = Appscript.app("iTunes.app")
src = itu.sources # dictionary acquired
itu.quit
itu.AS_app_data.target.reconnect # note this technique
src = itu.sources # dictionary still cached, not re-acquired
But if you run a different Ruby script, or run the same Ruby script on a later occasion, that of course is a completely different environment. Since rb-appscript has not, in some magic way, written the dictionary out to persistent storage (indeed, you might say that the scriptable application is the persistent storage!), we must now start all over with a clean slate, and rb-appscript acquires the target application’s dictionary afresh.
Since, as we have just seen, the dictionary is not directly human-readable, you’ll want to use some nice application that knows how to read the dictionary for you, parse it, and present its information conveniently. Naturally, AppleScript users have had such a way ever since AppleScript was invented; otherwise they wouldn’t have been able to use AppleScript! So I’ll mention some AppleScript-based ways of viewing the dictionary, even though the whole point of this book is that you’re not actually doing any coding in the AppleScript language. However, rb-appscript itself provides some Ruby-based ways of examining the dictionary, and I’ll talk about those as well.
If you have been an AppleScript user and you happen to have on hand a copy of Late Night Software Ltd.’s Script Debugger, this is, in my biased opinion, far and away the best dictionary presentation available. (My opinion is biased because I work for Late Night Software Ltd.) It is noteworthy for the way terms are categorized and presented, with extensive hyperlinking and cross-referencing, optional display of four-letter codes, diagrams of the inheritance and object model trees, and much more. Furthermore, Script Debugger’s Explorer view lets you see a running scriptable application’s entire object tree — its actual objects, along with their current property values and existing elements, at this moment — which is perhaps the best way to understand what information a scriptable application will provide and how to obtain it. Script Debugger is not inexpensive, and you wouldn’t want to purchase it just so you can see dictionaries; but if you already have it, use it.
The other main AppleScript-based way to read a dictionary is through Apple’s Script Editor, which is freeware and is present on your computer already. Script Editor’s presentation of the dictionary is not as good as Script Debugger’s, but over the years it has played a game of catch-up with Script Debugger, so it’s better than it used to be.
The big issue with using an AppleScript-based dictionary viewer is that you must mentally translate all the terminology from AppleScript to rb-appscript’s Ruby rendering of the terms. This is not really much of an issue; there are very few differences, and with a little practice you’ll be doing it automatically and unconsciously. The basic rules are:
Spaces and other non-alphanumeric characters are replaced by an underscore. So, for example, in the iTunes dictionary, AppleScript player state
becomes Ruby player_state
.
A term which, unaltered, would conflict with a “reserved word”, gets an underscore at the end. The reserved words are the methods already defined on Appscript::Application
and Appscript::Reference
. So, for example, in the iTunes dictionary, AppleScript id
becomes Ruby id_
, and AppleScript class
becomes Ruby class_
, because id
and class
are already Ruby methods; and in the Microsoft Word dictionary, AppleScript end
(a command parameter name) becomes Ruby :end_
, because end
is an Appscript::Reference
method (used in forming an insertion location; see Chapter 6).
There are some other terminology translation rules, but in practice they arise so rarely as to be not worth listing here.
As exemplified by the :end_
command parameter in Microsoft Word, you must also take responsibility for knowing when, in actual use, a name must be passed to rb-appscript as a Ruby symbol. Again, in practice this should provide no difficulty at all.
Hamish Sanderson also provides a utility application called ASTranslate (get it from the SourceForge repository, http://sourceforge.net/projects/appscript/files/). Here, you can enter AppleScript code, and ASTranslate will turn any Apple events that this AppleScript code would normally send into the corresponding Ruby expressions. For example, tell application "Finder" to set name of file 1 to "howdy"
is rendered as app("Finder").files[1].name.set("howdy")
. This can be a helpful quick shortcut for transforming AppleScript terminology into its rb-appscript equivalent.
ASDictionary is a utility application provided along with rb-appscript (get it from the SourceForge repository, http://sourceforge.net/projects/appscript/files/). Start it up and, in the window, check rb-appscript on the right side and, on the left side, check your preferred formatting options.
(My favorite options for presentation purposes are HTML (frames) and Compact classes. On the other hand, checking Plain text gives you a considerably more technical look at the dictionary’s actual contents — less human-readable, less convenient perhaps, but a more literal representation.)
Now use the Dictionary menu to choose an application whose dictionary you want to view. For example, you could choose Dictionary > Choose, locate and select iTunes, and click Choose. You can repeat that step to choose further applications. When you’ve selected the desired application(s), click Export. You’ll be prompted to select a folder into which all dictionaries will be rendered; do so, and click Choose.
Navigate in the Finder to the folder you selected, and open the generated file(s) to see the dictionary. Because you checked rb-appscript to start with, the terminology is displayed in Ruby; it’s ready for you to use, as shown.
As with the AppleScript presentation of the terminology, though, knowing when a name must be passed as a Ruby symbol is left up to you.
You can also use rb-appscript itself to access information through ASDictionary. To do so, call the help
method on any Appscript::Application
or Appscript::Reference
instance. ASDictionary must be present, and will be launched automatically.
ASDictionary fetches the dictionary efficiently; for example, if the scriptable application supplies its dictionary as a static 'aete'
resource, the scriptable application is not launched. Moreover, ASDictionary continues to cache the dictionary information in memory for as long as it runs, so that repeatedly using the help
command does not repeatedly ask the scriptable application for its dictionary.
By default (with no parameters), the help
method outputs the properties and elements of the object to which it is sent. You can chain plural class_
names to work your way down the object model tree, even though this isn’t the way you’d form a reference normally. For example:
itu = Appscript.app("iTunes.app")
itu.help # outputs property and element info for the :application class_
itu.sources.playlists.help # outputs property and element info for the :playlist class_
itu.current_encoder.help # outputs property and element info for the :encoder class_
itu.player_state.help # describes the player_state property of the :application class_
The help
command returns self
, so you can use several of them in a single chain, to output multiple chunks of information:
itu = Appscript.app("iTunes.app")
itu.help.sources.help.playlists.help # outputs info for :application, :source, and :playlist class_
Various options tell the help
command to output different things. These options are string parameters, so the syntax would be, for example, itu.help('-o')
.
-o (“overview”) outputs a simple list of all commands and classes.
-k (“keywords”) outputs a simple list of all built-in keywords. These are the datatypes defined in defaultterminology.rb (see Chapter 7) and have nothing to do with this particular application.
-t classname (“term”) outputs property and element info for the named class. This means, for example, that you don’t have to work your way down the object model just to get information on :playlist
as we did earlier; you can just say itu.help '-t playlist'
. Notice that the class_
name here is part of a string, not a symbol.
-t command outputs info for the named command.
-i (“inheritance”) outputs a diagram of the inheritance tree of classes for the application as a whole.
-i classname outputs a diagram of the branch of the inheritance tree that passes through the named class.
-r (“relationships”) outputs a diagram of the object model, starting at the object to which the help
command is sent (or, alternatively, you can say -r classname to start at that class), and proceeding to a depth of two. So, for example, the diagram for itu
includes the application’s sources
element and a source’s playlists
element; the diagram for source
includes the source’s playlists
element and a playlist’s tracks
element.
-s (“state”) outputs a list of the actual property and element values for the reference to which the help
command is send. You can filter this list to single property or element by using the syntax -s propertyOrElementName. You can fetch a lot of information this way. For example, Appscript.app("Finder.app").files.help('-s')
gives the name
of every file on the desktop, then the displayed_name
of every file on the desktop, then the name_extension
of every file on the desktop, and so on. This is a tremendously useful exploratory tool, a sort of static, text-based approximation of Script Debugger’s wonderful Explorer view.
A number of introspection instance methods are defined on the Appscript::Application
and Appscript::Reference
classes. These methods merely describe the instance to which they are sent, and constitute a lightweight means of obtaining an overview of what you can say to an instance. Unlike the ASDictionary-based help
method, they do not access the dictionary, they do not automatically output anything (they each return an array of strings), and they do not send any Apple events (except that rb-appscript will of course automatically acquire the dictionary, if it has not already been acquired, when you call one of these methods). Rather, they just take a quick peek inside the already cached terminology information for this application, and report back.
All of these methods return an Array of Strings (even if a term would be a symbol in actual use).
properties. Lists the names of the instance’s properties. These are property names that you can use as methods on the instance. (This is why, to ask a scriptable application object for its properties and their values as a hash, as described in Chapter 7, you have to use the properties_
method; the name properties
is “reserved”.)
elements. Lists the names of the instance’s elements. These are plural class_
names that you can use as methods on the instance.
commands. Lists the names of the application’s commands.
parameters(command). Lists the names of the named parameters for the given command. For example, itu.parameters('play')
returns ["once"]
.
keywords. Lists all of the application’s terminology except for command names.
This section describes the information you may expect to find in a dictionary.
A suite is a way of categorizing the terminology (classes and commands) within a dictionary. For example, in the iTunes dictionary, the play
command is part of the “iTunes” suite, but the open_location
command is part of the “Internet” suite. Suites have no implications whatever for what you can say to a scriptable application or how you say it. They are merely a way of organizing the human-readable presentation of the dictionary — and, in my opinion, a darned annoying way. When you look up information in Script Editor’s dictionary display, you are forced to start with suites. Since ex hypothesi you don’t know what suite “contains” what classes and commands, this just makes it harder to find information in the dictionary. I think suites are one of the worst inventions since sliced bread, and I propose to behave, everywhere in this book outside of this paragraph, as if they didn’t exist.
Comments are the dictionary author’s opportunity to provide plain-language advice and explanations within the human-readable presentation of the dictionary. (Comments are completely ignored by, and irrelevant to, processes that send Apple events to scriptable applications, such as rb-appscript and Apple’s Script Editor; they are present only to the human reader, studying the dictionary.)
Clearly, the dictionary author has a crucial opportunity to assist the human user through dictionary comments. Unfortunately the dictionary author is usually a programmer, not a teacher, who all too often fails to rise to this opportunity. For example, the dictionary comment on the :once
parameter of the iTunes play
command reads: “If true, play this track once and then stop.” Cool, but what if it’s false? (The rule turns out to be quite complicated, but surely a hint could have been provided.) And is the default value true or false, if this parameter is omitted? (The dictionary is silent on this score.)
Another problem is that the dictionary format itself has traditionally not been very amenable to providing useful, extensive comments. The 'aete'
structure lets the dictionary author provide only brief plain text strings. Fortunately, the advent of the 'sdef'
dictionary admits of much longer comments, consisting of multiple paragraphs and HTML formatting. Unfortunately, few applications are using the 'sdef'
format hitherto, and of those that do, few take advantage of extended dictionary comments as a form of documentation. Even worse, most dictionary presentations do not show you the extended comments even when they are present. (This is yet another respect in which the Script Debugger dictionary presentation is superior.)
A class in the dictionary is of course what this book calls a class_
, a value type in the scriptable application’s world. The dictionary lists class_
types defined by the scriptable application itself; a built-in class_
, such as :string
or :integer
(i.e. a class_
defined in defaultterminology.rb, and discussed in Chapter 7), is generally not listed in a scriptable application’s dictionary. So, for example:
itu = Appscript.app("iTunes.app")
itu.help('-o')
# === and here is part of the output ===
Classes:
application
artwork
audio_CD_playlist
audio_CD_track
browser_window
device_playlist
device_track
encoder
EQ_preset
EQ_window
file_track
folder_playlist
item
library_playlist
playlist
playlist_window
print_settings
radio_tuner_playlist
shared_track
source
track
URL_track
user_playlist
visual
window
A scriptable application that defines a class_
is free to define it however it likes, even if other scriptable application’s define a class_
by the same name. The iTunes :application
and the Finder :application
have little in common. A Finder :folder
is not the same as a Microsoft Entourage :folder
. And so on.
A class_
definition in the dictionary consists of its plural, its inheritance (if any), its properties, and its elements. So, for example:
itu = Appscript.app("iTunes.app")
itu.help('-t playlist')
# === and here is part of the output ===
Class: playlist -- a list of songs/streams
Plural:
playlists
Inherits from:
item (in iTunes Suite)
Properties:
container (r/o) : reference -- the container of the item
id_ (r/o) : integer -- the id of the item
index (r/o) : integer -- The index of the item in internal application order.
# ...
Elements:
tracks -- by index, name, id
The plural is used in forming an element specifier (see Chapter 5). Occasionally, a dictionary may neglect to define an explicit plural, and in that case (despite Chapter 5) you may have to use the singular to form an element specifier; more on this later on.
The inheritance is used as a way of stating that a class_
is the same as another class_
with the addition of one or more properties. This is a fairly crude notion of inheritance, but with some planning it may allow a dictionary author to implement a useful inheritance tree. The iTunes inheritance tree is quite nicely implemented:
itu = Appscript.app("iTunes.app")
itu.help('-i')
# === output ===
Inheritance for all classes:
-print_settings
-application
-item
|-artwork
|-encoder
|-EQ_preset
|-playlist
| |-audio_CD_playlist
| |-device_playlist
| |-library_playlist
| |-radio_tuner_playlist
| |-user_playlist
| |-folder_playlist
|
|-source
|-track
| |-audio_CD_track
| |-device_track
| |-file_track
| |-shared_track
| |-URL_track
|
|-visual
|-window
|-browser_window
|-EQ_window
|-playlist_window
Almost everything inherits from :item
, which has a persistent_ID
property that is particularly appropriate to a database-like application such as iTunes, uniquely identifying every playlist and track forever. Additionally, :item
(and therefore almost everything else) has a container
property which allows you to work your way up the container chain of a reference (would that every scriptable application did this!):
itu = Appscript.app("iTunes.app")
cur = itu.current_track.get
p cur.name.get #=> "Broke and Hungry Blues", the track name
cur = cur.container
p cur.name.get #=> "blues", the playlist name
cur = cur.container
p cur.name.get #=> "Library", the source name
You can also see from the diagram that a number of class_
types are broken down through inheritance into more specific types. The difference between the specific types might be as small as the presence or absence of a single property. For example, as I mentioned in Chapter 5, a :file_track
has a location
property (pointing to the file), whereas a :URL_track
has an address
property (the URL).
It’s important to understand, though, that by no means all scriptable applications use inheritance in this way. The iTunes developers could have implemented a single :track
class_
having both a location
property and an address
property, with only one of those properties having a meaningful value for any particular track. Every scriptable application is different.
The listing for each property of a class_
states whether that property is read-only, meaning that an attempt to set
that property should result in an exception. We have already seen this in the help
output for :playlist
:
Properties:
container (r/o) : reference -- the container of the item
id_ (r/o) : integer -- the id of the item
index (r/o) : integer -- The index of the item in internal application order.
# ...
The parenthetical “(r/o)” means read-only. A property without this annotation is settable by default.
The listing for each element of a class_
uses a plural class name, which is the very term you would use to form an element specifier.
In a scriptable application with inheritance, where it is legal to specify an element by various class_
types that inherit from a common class_
, the common class_
might be the only one listed. As we have already seen, the iTunes dictionary does this. The only element listed for :playlist
is tracks
:
Elements:
tracks -- by index, name, id
Nevertheless, when you ask for a reference to a track, the reference might speak of one of the inheriting class_
types, such as file_tracks
:
itu = Appscript.app("iTunes.app")
p itu.playlists[1].tracks[1].get
#=> app("/Applications/iTunes.app").sources.ID(43).library_playlists.ID(39717).file_tracks.ID(53709)
And, even more important, it is perfectly legal to use a file_tracks
element specifier with a playlist, even though the dictionary does not say so:
itu = Appscript.app("iTunes.app")
p itu.playlists[1].file_tracks[1].get
#=> app("/Applications/iTunes.app").sources.ID(43).library_playlists.ID(39717).file_tracks.ID(53709)
But not every dictionary behaves that way. For example, the Finder :application
elements include windows
and Finder_windows
even though the latter inherits from the former. This way of structuring the dictionary tells you more about what you can actually say when forming a reference or element specifier.
Elements:
...
windows -- by index, name
Finder_windows -- by index, id
clipping_windows -- by index, name
Element listings also state the specifier forms that are legal for that element (see Chapter 5 for element specifier forms). Here’s an example from the Finder dictionary:
Elements:
items -- by index, relative, name, range, test
containers -- by index, name
disks -- by index, name, id
folders -- by index, name, id
...
Dictionary authors, however, rarely bother to set up these lists correctly, so they are rarely of any use, and should generally be ignored. In the above example, containers
inherits from items
, and can be perfectly well specified by relative position, range or Boolean test, so that list is wrong; and a :folder
has no id_
property and cannot be specified by ID, so that list is wrong.
It is also possible for an element listing to state how that element can be accessed — whether it is possible to get
one, to make
a new one, and to delete
an existing one. However, this is a feature of the new 'sdef'
dictionary format, which few scriptable applications use, and in any case most dictionary presentations do not display it (only Script Debugger does, as far as I know).
A command in the dictionary is listed with the names of its parameters (the direct parameter, if any, is shown first, with no name) and an indication of whether each parameter is optional. The command’s result, if any, is also shown. For example (some of the comments are curtailed, for clarity):
f = Appscript.app("Finder.app")
p f.help('-t duplicate')
# === output ===
Command: duplicate -- Duplicate one or more object(s)
reference -- the object(s) to duplicate
[to : location_reference] -- the new location for the object(s)
[replacing : boolean] -- Specifies whether or not to replace items ...
[routing_suppressed : boolean] -- Specifies whether or not to autoroute items (default is false)...
Result: reference -- to the duplicated object(s)
In the help
display above, a parameter is optional if its name is in square brackets. Thus, in the example, the direct parameter is required, and the other three parameters are listed as being optional.
The help
display shows bare parameter names, even though in actual use you will supply them as symbols. Thus, the above example says replacing
, but in supplying the named parameter hash when using the command, you would say :replacing
. This should not be a source of any difficulty.
class_
TypeA property listing includes the class_
of the value you can expect to receive when you get
that property, or that you are expected to provide when you set
that property, as in this excerpt from the help
output for :file
in the Finder (for clarity, the comments are omitted here):
...
extension_hidden : boolean
index (r/o) : integer
container (r/o) : reference
disk (r/o) : reference
position : point
desktop_position : point
bounds : bounding_rectangle
...
Similarly, a command listing shows the expected class_
of each parameter and of the command’s result.
Command: duplicate -- Duplicate one or more object(s)
reference -- the object(s) to duplicate
[to : location_reference] -- the new location for the object(s)
[replacing : boolean] -- Specifies whether or not to replace items ...
[routing_suppressed : boolean] -- Specifies whether or not to autoroute items (default is false)...
Result: reference -- to the duplicated object(s)
Most of these class_
types are either types defined by the scriptable application (and listed when you say help('-o')
) or they are built-in types discussed in Chapter 7. But some of them are built-in types we haven’t yet discussed; these are terms that exist solely for this purpose, that is, they are intended specifically for use in describing a class_
type in the dictionary. Such types must be expressly defined because, like everything in the dictionary resource, they are encoded as four-letter codes. Translation between the four-letter codes and their English terminology equivalents is performed as part of the human-readable dictionary display; in rb-appscript, these equivalences are part of defaultterminology.rb, the relevant lines of which are quoted in this discussion.
anything ('****' => :anything
). This functions as a wild-card type, typically where the specifics about the actual class_
types are too complicated or too unpredictable to be expressed in any other way. Might also appear as any. Example, from BBEdit:
Class: text_document -- A text document
Properties:
...
contents : anything -- contents of the document (if any)
reference ('obj ' => :reference
). This is a slightly less wild wild-card type, indicating an object specifier (equivalent to an Appscript::Reference
). Might also appear as specifier. For an example, see the listing for the duplicate
command above; the result is described as a reference
.
location_reference ('insl' => :location_reference
). This is synonymous with (and might appear as) insertion location, discussed in Chapter 6 in the section “Make and Insertion Locations”. (It might also appear as location specifier.) For an example, see the listing for the duplicate
command above; the :to
parameter is described as a location_reference
.
type_class ('type' => :type_class
). This means a class_
. As you know, a class_
name is passed as a symbol in rb-appscript. The make
command, discussed in Chapter 6 in the section “Make and Insertion Locations”, is an example:
Command: make -- Make a new element
new : type_class -- the class of the new element
...
The dictionary format allows for two further tweaks to a class_
type specification:
list of. This lets the dictionary give both a class_
type and the additional information that a list of objects of that type is expected. Here’s an example from the iTunes dictionary:
Command: add -- add one or more files to a playlist
list of alias -- the file(s) to add
...
or. This lets the dictionary list multiple class_
type alternatives. This is an extremely helpful and powerful device, but unfortunately it is so clumsily implemented in the 'aete'
dictionary format that it is almost never used. With the advent of the 'sdef'
format, expression of alternatives is very easy, and we may expect increased use of it. There will also need to be some improvement in the way human-readable dictionary presentations show alternatives. The rb-appscript help
command does a rather messy job of it, turning the whole class_
type into a single long term, as in this example from the Script Debugger dictionary:
Command: open -- Open a document.
list_of_file_or_list_of_specifier -- The file(s) or object(s) to be opened.
...
In addition to the possibilities mentioned in the previous section, a class_
type might be an enumeration. An enumeration is a list of terms (called enumerators), any one of which is legal as a value. In the dictionary itself, the class_
type is the enumeration, and elsewhere in the dictionary the enumeration is defined in terms of its enumerators. In most dictionary presentations, however, these two listings are collapsed into one, with the list of enumerators being shown directly as the type. Here’s an example from the iTunes search
command:
Command: search -- search a playlist for tracks matching the search string.
playlist -- the playlist to search
for : unicode_text -- the search text
[only : :albums / :all / :artists / :composers / :displayed / :songs] -- area to search (default is all)
Result: track -- reference to found track(s)
The :only
parameter is an enumeration, whose enumerators are shown. The enumerators are separated by a forward slash, and are shown as symbols, which is exactly how you will use them:
itu = Appscript.app("iTunes.app")
result = itu.playlists["Library"].search :for => "Lemon", :only => :composers
Unfortunately, this way of displaying enumerators throws away some possibly useful information, namely any comments that may accompany each enumerator. For this reason, enumerators have traditionally not had useful comments (the dictionary author thinks, “There’s no point writing a comment that the user will never see”). But the mere name of an enumerator by no means always makes its meaning self-evident, and useful enumerator comments are becoming more frequent (iTunes actually has some, for example). Script Debugger’s human-readable dictionary presentation has long displayed enumerator comments, with Apple’s Script Editor following suit; the rb-appscript help
command and the ASDictionary HTML dictionary presentation don’t, but ASDictionary’s text dictionary presentation does (as I mentioned earlier, it’s harder to read but technically more informative).
If you’re hoping that a scriptable application’s dictionary will function as a complete guide to scripting that application, abandon hope. The dictionary itself, especially in its legacy 'aete'
format, is inadequate as a medium for communicating all you need to know; and dictionary authors are generally not careful to take full and accurate advantage of what communicative capacity the dictionary does have. Without belaboring the matter (which I have already done, at length and in detail, in my AppleScript book), here is a survey of some of the dictionary’s most glaring deficiencies.
Coercions are not listed in the dictionary. It may be legal to supply a command parameter of some other class_
type than the dictionary specifies, because the scriptable application can coerce it to the desired type. Or, you may be able to request a command result as an alternative class_
type, using the :result_type
parameter. But the dictionary tells you nothing of this.
Shortcut reference forms are not listed in the dictionary. (See “Shortcut References” in Chapter 5.) For example, looking at the iTunes dictionary, you would think that the only way to specify a playlist is as an element of a source; you have no way to discover that you can specify a playlist as an element of the :application
object (and that the "Library"
source will be supplied for you).
A dictionary may neglect to supply the plural for a class_
type, and when it does, you must use the singular in an element specifier. An example is GraphicConverter’s :window
type; I think the problem here is that GraphicConverter expects the plural to be “inherited” from the built-in AppleScript definition of :window
, but rb-appscript is more literal and takes the dictionary at its word. It’s arguably a good thing that rb-appscript doesn’t sloppily cover up deficiencies in the dictionary, but it means that you, the user, have to be a little more alert (and I do in fact regard this as a bug in rb-appscript).
A dictionary may omit important facts, even when it is perfectly capable of displaying them. This leaves you none the wiser. For example, in the Finder, a Finder window can have items:
p Appscript.app("Finder.app").Finder_windows[1].items[1].name.get #=> "ASDictionary.app"
But the dictionary fails to mention that Finder windows can have any elements at all!
Also, I’ve already pointed out, information such as what specifier forms can be used to refer to a particular type of element are notoriously inaccurate.
The way the dictionary lists an expected class_
type, especially for a command parameter, often falls greatly short of the truth. For example, the iTunes dictionary lists the play
command direct parameter as a reference
, but a reference to what? Clearly you can’t play just any old thing; for example, you can’t play a source. But you can play a playlist, even though the dictionary gives you no hint of this fact. Similarly, in the Finder, the eject
command direct parameter is a reference
. But of course it really needs to be a reference to a disk.
Part of the problem is that the dictionary format is inadequate to state the truth. For example, the iTunes convert
command’s direct parameter is described as a “list of reference”, but what is wanted is a track or an alias, and it needn’t be a list (though it can be). There is no way to say “a track or alias, or a list of those”, so the dictionary is left waving its hands.
But sometimes a dictionary will just lie. One of my favorite examples is the save
command in GraphicConverter’s dictionary:
Command: save -- Save an object
reference -- the object to save
[in : alias] -- the file in which to save the object
...
That’s obviously a lie, because you can’t make an alias to a non-existent file, so you’d have no way to save a new document. Experimentation shows that what GraphicConverter really wants is a pathname string:
gc = Appscript.app("GraphicConverter.app")
f = "/Users/mattleopard/Desktop/testing.png"
gc.window[1].save :in => f, :as => :PNG # and note use of singular in element specifier
Another good example of a lie is the failure of the Finder’s dictionary to mention that the reveal
command returns a value (a very useful value, a reference to what was revealed). Yet another is that the Finder says that a file’s creator_type
property is a :type_class
, whereas it is in fact a Unicode text (a Ruby String).
In Chapter 7 we saw that a record can be treated in the dictionary as it were a class_
type defined by the scriptable application. This is certainly a form of lying, especially since it makes a difference to how you treat the resulting value. For example, BBEdit’s dictionary behaves as if :tag_result
were a class_
even though it really is just a record, and there is no way to discover the truth from the dictionary (except by guesswork — in particular, you might suspect the truth if you happened to notice that :tag_result
has no plural, has no elements, and is not an element of anything).
The existence of the dictionary does nothing to prevent you from saying nonsense in an Apple event.
In general it’s important to understand that there is very little sanity checking at the front end (the front end being, in this case, rb-appscript). Nothing stops you from supplying a parameter of a blatantly incorrect type. Nothing stops you from speaking of an element or property of an object of some class_
even when that class_
doesn’t have that element or property. The dictionary is not treated behind the scenes as prescriptive; rb-appscript uses it to discover what terms exist, and that’s about all. This should not come as much of a surprise, though, to users of Ruby, which is itself such a dynamic language.
Having gotten past the front end, you may find that there is not all that much sanity checking at the back end either (the scriptable application itself). The following, for example, is perfectly legal:
f = Appscript.app("Finder.app")
f.eject(f.files[1].files)
The Finder allows you to say that, and doesn’t generate an exception. It doesn’t do anything in response; it just fails silently. You can’t eject a file; even more important, a file has no files
element — indeed, it has no elements at all.
Similarly, with enumerations you can pass, say, an enumerator from the wrong enumeration, and sometimes you can get away with it:
itu = Appscript.app("iTunes.app")
result = itu.playlists["Library"].search :for => "Lemon", :only => :user
iTunes should probably complain about that, but it doesn’t; it silently ignores the :only
parameter, and performs the search.
The greatest flaw of the dictionary is that mere terminology is not the same as how you use that terminology to accomplish a desired task. A knowledge of bare words, which is all that the dictionary communicates (and badly, at that), is not a performative or illocutionary knowledge; it is not a knowledge of what Austin famously called “how to do things with words.” To see the problem, it is sufficient to observe the great number of user messages on AppleScript-related mailing lists and forums begging for enlightenment on how to formulate the mysterious incantation that will persuade some scriptable application to perform some desired action. Here are paraphrases of some actual recent user messages:
“In Microsoft Word, I want my script to search for a word in the document. If that word is found, the style of the whole paragraph must be changed to Comment. How do I do it?”
“In Safari, I want my script to check a checkbox on a sheet opened from the save command of the view source window — but only if the checkbox is not already checked. How do I do it?”
“In iTunes, I want my script to play a song from a shared music library, but for it to find the song to play, the shared music library has to be loaded first. If it isn’t loaded there is an error because it can’t find the song. So I want to tell iTunes to load the shared music library. How do I do it?”
“In Terminal, my script ssh’es to one machine, then calls
screen -r
. However in that same window I want to open up another tab, and ssh to a different machine and call a different screen. How do I do it?”“I need my script to launch Safari, go to a certain website, enter a username and password and press the ‘Login’ button. How do I do it?”
Problems like these can’t be solved by using the dictionary alone. If they can be solved at all (which is by no means certain a priori, since not everything you can make an application do with the mouse and keyboard is exposed through its scriptability API), the solution will involve a knowledge of tools and techniques well beyond what a dictionary can include. Acquiring this knowledge of tools and techniques is the true challenge of scripting.
You’re looking at a draft of a chapter from a work in progress, tentatively titled Scripting Mac Applications With Ruby: An AppleScript Alternative, by Matt Neuburg.
Covers rb-appscript 0.6.1. Last revised Jun 23, 2012. All content ©2012 by the author, all rights reserved.
This book took time and effort to write, and no traditional publisher would accept it. If it has been useful to you, please consider a small donation to my PayPal account (matt at tidbits dot com). Thanks!