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.


Prev: Datatypes
Next: Scripting Additions
Contents

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

3.4. Introspection Methods

4. Contents of a Dictionary

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. Dictionary Deficiencies

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

1. How the Dictionary is Formatted

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:

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

2. How rb-appscript Accesses the Dictionary

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.

3. Getting a Look at the Dictionary

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.

3.1. Script Debugger and Script Editor

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:

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.

3.2. ASDictionary

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.

3.3. Using ASDictionary Within Ruby

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

3.4. Introspection Methods

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

4. Contents of a Dictionary

This section describes the information you may expect to find in a dictionary.

4.1. Suites

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.

4.2. Comments

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

4.3. Classes

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.

4.4. Properties

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.

4.5. Elements

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

4.6. Commands

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.

4.7. Expected class_ Type

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

The dictionary format allows for two further tweaks to a class_ type specification:

4.8. Enumerations

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

5. Dictionary Deficiencies

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.

5.1. Coercions

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.

5.2. Shortcuts

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

5.3. Plurals

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

5.4. Missing Facts

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.

5.5. Expected Types

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

5.6. Nonsense Allowed

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.

5.7. Tools and Techniques

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.


Prev: Datatypes
Next: Scripting Additions
Contents

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!