And just as the consistency of its user interface has been perhaps the most important factor in the Macintosh computer's ongoing adoption and success, consistency is an essential part of the world of scripting. It's highly important for users (by which I mean anyone who writes scripts, including power users, solutions providers, consultants, in-house developers, resellers, and programmers) to feel as if they're using a single language, regardless of which application they're scripting. As a developer, you have a responsibility to extend the AppleScript language in a consistent manner.
My purpose in this article, which might be considered a first attempt at some "human scriptability guidelines," is to offer conventions, suggestions, and general guidelines that you can follow to maintain consistency with the AppleScript language. I also give some suggestions for redoing a poorly done scripting implementation. (I'm assuming you're already convinced that you should make your application scriptable; if you're not, see "Why Implement Scriptability?") The result of doing all this work is that the AppleScript language feels consistent across applications of different types produced by different vendors.
It's important to implement AppleScript support in your core application, rather than through an external API, as some databases such as 4th Dimension and Omnis do. When your core application isn't Apple event-aware, two things happen: (1) no dictionary resides in the application itself, and (2) functionality is usually limited. Users have difficulty doing decent scripting of these applications, by and large. If you simply must support Apple events through an external API, at least support the dynamic terminology mechanism for your extensions.
The bottom line is this: If your application isn't scriptable soon, you'll be left out in the cold. If you do the work now, not only will you open up more uses for your application in the "big picture," but you'll also be that much closer to implementing what you need in order to support several other technologies. So please, don't put it off!
It's important to note that a well-designed Apple event structure greatly increases the ease of scripting your application. In a minute I'll say more about that, but first let's look at the basic anatomy of an AppleScript command.
verb [noun] [keyword and value] [keyword and value] . . .
These elements of sentence construction can be thought of as parts of speech that make up a human-oriented computer language. Here are a couple of examples of commands:
close the front window saving in file "Goofballs:Razor" set the font of the first word in the front window to "Helvetica"Let's dissect these:
close verb, corresponding to kAECloseElement the front window noun, corresponding to keyDirectObject typeObjectSpecifier) saving in keyword, corresponding to keyAEFile file "Goofballs:Razor" value, of typeFSS set verb, corresponding to kAESetData the font of the first word noun, corresponding to keyDirectObject in the front window (typeObjectSpecifier) to keyword, corresponding to keyAEData "Helvetica" value, of typeWildCardNote that for application-defined commands, a verb -- for example, close or set -- is the human language representation for the action described by an Apple event (which I often shorten to just event), so there's a general correspondence between Apple events and verbs. In this article, I identify Apple events by the event's name, its 4-byte ID, or the constant name for the ID. For example, the Close Element event has the ID 'clos' and the constant name kAECloseElement, and corresponds to the AppleScript verb close; the Set Data event has the ID 'setd' and the constant name kAESetData, and corresponds to the AppleScript verb set.
Your ability to guide users toward writing clean, natural-language statements depends a great deal on your use of the object model, as I explain next.
tell application "My Charter" Plot Options myOptions Set Axis Lengths for X 100 for Y 100 Output PICT Plot chart "pie" end tellBy contrast, the script below describing the same operation in much more familiar terms results when the application uses familiar objects and characteristics of objects (properties):
tell application "My Charter" make new chart tell chart 1 set the type to pie set the x axis to 100 set the y axis to 100 end tell end tellAs illustrated by this script, a principal indication of solid use of the object model is that the most common verbs used in scripts are make, set, and get.
Users are more likely to remember the terms for objects than commands. Moreover, from the user interface, they often use Command-key shortcuts for the actions instead of looking at the menu items once they get comfortable using your application. If you don't implement the standard commands, they'll probably need to go back to your application's menus to find out that the menu command is, for instance, Plot Chart. You can help them by making the scripting terms intuitive. For instance, they already know what a chart is, and they're familiar with the standard AppleScript verbs make and set, which they're using to script other applications. Thus, the second script above will feel like an extension of the same language used in scripting other applications, while the first script won't.
Now consider this partial list of custom verbs from a popular mail application that doesn't follow the object model:
AddAttachment SetSubject GetSubject AddTo SetText GetText AddCC SetReceipt GetReceipt AddBCC SetPriority GetPriority AddToAtPO SetLog GetLogNotice some patterns here? All of them start with Add, Set, or Get -- and this isn't even a complete list of all the commands in this application starting with these verbs. It's definitely time for this application to go with the object model. Most of the above commands can be replaced by set and get commands applied to properties such as subject, receipt, priority, log, and so forth.
Generally, you won't want the user to script interface elements, such as dialog box items (whose meaning should be expressed through verbs, or properties of the application or your objects), but rather objects that either contain or represent the user's data (which I'll call containers and content objects). For example, an object model might incorporate documents (containers); graphic objects (containers or content objects); forms (containers) and the elements of a form, such as fields (content objects); cells in a spreadsheet or database (content objects); and text elements, like paragraphs, words, and characters (content objects).
You should think carefully about whether to make something an object or a property; this is discussed later in the section "Other Tips and Tricks."
As programmers, we have the notion that an object "owns" its methods; we think in terms of sending messages to an object. For instance, the following C++ code fragment sends several messages to one object:
CDocument::Print CDocument::Close CDocument::Save CDocument::DeleteBy contrast, users think about doing some action to an object. So when you design your commands, you should think about allowing verbs to be applied to many different types of objects, as illustrated here:
print document "Fred" print form ID 555 print page 4Examine the actions that users take with your application and the objects that the actions are taken on. This will lead you naturally to an effective object model design.
Before I say any more about this approach, you should note these two very important caveats:
Ideally, you should allow users to achieve through scripting everything that they can with your user interface. To accomplish this, you should think of capabilities you would like users to be able to script that go beyond your menu commands, such as capabilities accessible only from tools in a palette or actions resulting from a drag and drop operation. On the other hand, it's not entirely necessary to make the capabilities available from your user interface identical to those controllable through scripting. Scripting is a different interface into your program, so it's OK to do things a bit differently.
For example, you don't have to create exactly one script statement corresponding to each user action. If a single menu item or button in your application results in a complex action or more than one action, it might produce clearer scripting or give more flexibility to allow the user to perform individual portions of the action through separate statements in a script. Conversely, it can also be better to combine more than one action into one statement, especially when the set of actions is always performed in the same sequence.
Also, actions that aren't even possible from the user interface can often be made scriptable. For example, the Scriptable Text Editor allows a script to make a new window behind the front window, something that the user normally can't do. You could also provide a method of accomplishing a task that's too complex or impossible to express through manipulation of objects on the screen.
The aete editor stack and the include files for Rez are available on this issue's CD and as part of the AppleScript Software Development Toolkit from APDA. Resource editors with good 'aete' editors are commercially available.
Details of the structure and format of an 'aete' resource can be found in Chapter 8 of Inside Macintosh: Interapplication Communication.
For instance, Figure 1 shows part of the object containment hierarchy for an imaginary application that contains text windows, folders, and a connection. The windows can contain one or more paragraphs, words, or characters; paragraphs can contain words or characters; and words can contain characters. Note that even though only one connection is possible for this particular application, connection is an object class contained by the application, as opposed to being merely a property of the application.
Figure 1. Part of a typical object containment hierarchy
It's important to connect up all the appropriate pieces of your containment hierarchy. It's especially important to hook up the main classes of objects -- such as windows, documents, and other special objects not contained by other objects -- to the top level of the hierarchy by listing them as elements of your application. Never "orphan" a class! Every object class (except the application) must be listed as an element of something. Most classes or objects are contained by another object. If any object can't be contained by another object, it must be contained by the application.
The AppleScript terms (commands, objects, and properties) that you'll use in your vocabulary fall into two categories:
To ensure that your scripting implementation will have as much consistency across applications as the user interface, you should use the standard terms whenever possible. As you've seen, this is inextricably tied to good object model design. See "Registry Suites" for descriptions of the standard suites. Unless you have a excellent reason, don't vary from the standard terms associated with these suites.
Other Apple event suites that are used less frequently include the following:
The Word Services, QuickDraw, and QuickDraw Supplemental suites are generally not used in scripting.
To look up the accepted human-language constructs for the Required, Core, Text, Table, and QuickDraw suites, see the file EnglishTerminology.r (also available for French and Japanese); for the Database suite, see the file Database.aete.r; and for Miscellaneous Standards, see EnglishMiscellaneous.r (also available for French and Japanese). These files, which present the standard terms in the form of 'aete' resource templates (in Rez form), can be found on this issue's CD and are included in the AppleScript Software Development Toolkit.
Listing 1. Sample Rez code supporting an entire suite
"Required Suite", /* The entire suite, as is */ "Terms that every application should support", kCoreEventClass, /* 'reqd' */ 1, 1, { /* array Events: 0 elements */ }, { /* array Classes: 0 elements */ }, { /* array ComparisonOps: 0 elements */ }, { /* array Enumerations: 0 elements */ },Note that whenever you use the 4-byte suite ID for a suite itself (as opposed to the suite ID for the individual events in a suite), all the standard definitions for that suite will automatically appear in your dictionary. Do not use this technique if you're implementing only a few of a suite's Apple events or objects. And note that this technique works only for the Required, Core, Text, Table, and QuickDraw suites, which are in AppleScript's 'aeut' resource. For all other suites, you'll need to include all the details of the suite in your 'aete' resource if you support it in its entirety.
Listing 2. Sample Rez code supporting an entire suite to be modified
"Standard Suite", /* The entire suite, plus an extra parameter */ "Common terms for most applications", kAECoreSuite, /* 'core' */ 1, 1, { /* array Events: 1 element */ /* [1] */ "print", /* This is the event being extended. */ "Print the specified object(s)", kCoreEventClass, kAEPrint, ... { /* array OtherParams: 1 element */ /* [1] */ "copies", /* This is the parameter being added. */ 'NCOP', 'shor', "The number of copies to print", ... } }, { /* array Classes: 0 elements */ }, { /* array ComparisonOps: 0 elements */ }, { /* array Enumerations: 0 elements */ },Supporting part of a suite. On the other hand, when you want to implement only part of a suite, you need to explicitly define the subset of the suite's events and objects that you support. For example, let's say you implement only seven of the events in the Core suite (which nearly everyone implements only partially; these seven are the minimum you should support). You'll create a new suite with a unique ID -- your application's signature, perhaps, or, as used by the Scriptable Text Editor, 'CoRe' (note the alteration from all lowercase, which prevents the whole Core suite from appearing automatically). Then you'll include the events and objects you want. Listing 3 shows how to do this in Rez code. Note that you should retain the original suite ID of 'core' for the individual Apple events (except for Open, Print, and Quit, which get 'aevt', as mentioned earlier in "Registry Suites"), both in your 'aete' and in your Apple event handlers.
Listing 3. Sample Rez code supporting a partial suite
"Subset of the Standard Suite", /* Only seven of the Core events */ "Common terms used in this application", 'CoRe', /* Note uppercase alteration of the 'core' suite ID. */ 1, 1, { /* array Events: 7 elements */ /* [1] */ "count", "Return number of elements of a particular class ...", kAECoreSuite, kAECountElements, ... /* [2] */ "delete", "Delete an element from an object", kAECoreSuite, kAEDelete, ... /* [3] */ "exists", "Verify if an object exists", kAECoreSuite, kAEDoObjectsExist, ... /* [4] */ "get", "Get the data for an object", kAECoreSuite, kAEGetData, ... /* [5] */ "make", "Make a new element", kAECoreSuite, kAECreateElement, ... /* [6] */ "quit", "Quit an application program", kCoreEventClass, /* Open, Print, and Quit have 'aevt' suite ID. */ kAEQuitApplication, ... /* [7] */ "set", "Set an object's data", kAECoreSuite, kAESetData, ... }, { /* array Classes ... }, ...
The terms you create that aren't in the standard suites are actually extensions to AppleScript. The nature of these terms will directly affect the experience your users will have in scripting your application. You should create terms that give users the feeling that they're working within a unified language.
Keep in mind that creating new object classes or properties is generally better than creating new verbs. If you do need to create your own verbs or use terms unique to your application, it's better to try to do it in the spirit of what's been done before instead of inventing your own "language within a language." Users shouldn't feel as if they're jumping between what appear to be separate "pseudo-languages" for each application.
Although early documentation from Apple suggested creating one custom suite containing your Core suite subset lumped together with your custom verbs, I don't always recommend this. If you're adding a lot of vocabulary, either new events or objects, you can make your dictionary more understandable by keeping the Core subset in one suite and defining your own new verbs in a separate suite. In fact, it's OK to make more than one custom suite if you have a great many new verbs or objects and if you can separate them into distinct functional groupings.
Make sure that the names for your new suites clearly indicate that they're custom suites or specific to your application. And when you create ID codes for your new events, objects, and such, remember that Apple reserves the use of all 4-byte codes that contain only lowercase letters, so you should use at least one uppercase letter in the codes. There isn't yet a way to register your codes, but the Webster project (described at the end of this article in "Resources") aims to serve that end.
The drawbacks to Do Script are that (1) new users must learn a new language -- yours -- in addition to AppleScript, and (2) Do Script is a one-way communication in most cases -- the script can control your application, but it acts much more like a puppeteer than a team leader. In the end, Do Script defeats the purpose of a single language for controlling all applications.
Another easy method of appearing to be scriptable is to implement a Do Menu event, in which a user can simulate pulling down a menu and selecting menu items. Again, this is no substitute for real scriptability.
By the way, if you're thinking about creating a new scripting language internal to your application, think again. The world doesn't need yet another private application-specific language. AppleScript is there for you, with all of its rich expressiveness, to use as your own. The benefit is that by the time you complete your scripting support, many of your users will already be familiar with AppleScript.
Begin terms with lowercase.
Begin all the terms in your dictionary with lowercase letters, except for proper names like PowerTalk. It may seem trivial, but it's actually quite important. If you use uppercase letters to begin your object names, for example, you'll end up with strange-looking commands that contain a mixture of uppercase and lowercase letters:
make new History set the Title of the first History to ...Using all lowercase letters gives a more consistent look:
make new history set the title of the first history to ...
ReplaceAll set the TransferProtocol to ConvertFromMainframeInstead, make them flow naturally:
replace all set the transfer protocol to convert from mainframeUse familiar terms, but avoid reserved words.
Generally speaking, you'll want to identify your object classes with terms your users are already familiar with. When it comes to your verbs, you can use many of your menu items, and for the rest use terms that will be familiar and that lend themselves to starting clean and natural statements. Plain human language is always preferable to C- or Pascal-style identifiers.
On the other hand, when you attempt to use familiar terms, keep in mind that the list of words that could potentially conflict with your dictionary is constantly growing and also depends on which scripting additions and applications are currently running on a particular computer. As a result, there's no official list of reserved words to avoid. Choose your terms with extreme care -- remember, you're actually extending the language and what you do here will affect the future.
In summary, try to provide words that are familiar to users without running into conflicts with existing terminology. Don't make up new terms to express something when there's a clean way to do it using existing terminology: where possible, use terms analogous to those already in use to represent constructs (verbs, parameters, objects, properties, and enumerators) in your application. Conversely, don't use existing terms to represent something that differs from a term's accepted use.
Use lots of enumerations.
Very few developers have made effective use of enumerations. An enumeration is a set of constants, usually representing a fixed set of choices. In AppleScript, these constants, known as enumerators, are identified (like everything else) by 4-byte ID codes. Use an enumeration as the type for a parameter or property whenever there's a choice to be made from a specific list of possibilities, and make sure you use natural language.
For example,
set status to 1or
set status to "warm"isn't as helpful to the script writer as
set status to warmThis subtle change makes a great deal of difference. In the dictionary, the enumeration is displayed as "hot|warm|cool|cold," as opposed to "integer" or "string," and the user can easily see there's a choice. To accomplish this, you would create an enumeration with the enumerators hot, warm, cool, and cold, and use the 4-byte enumeration ID as the type for the status property of the class, as shown in Listing 4. The dictionary entry for this property will read "status hot|warm|cool|cold," instead of "status integer" or "status string."
It's an extremely common mistake among developers to try using ordinal values as enumerators, but it simply won't work. Unlike in C or Pascal, you can't use ordinal values -- you must use 4-byte ID codes.
Listing 4. Creating and using an enumeration
{ /* array Properties: ... /* [1] */ "status", 'Psta', /* Note uppercase in your IDs. */ 'Esta', /* The enumeration's ID */ "the status", reserved, singleItem, enumerated, /* Use "enumerated" */ ... }, ... { /* array Enumerations: 1 element */ /* [1] */ 'Esta', { /* array Enumerators: 4 elements */ /* [1] */ "hot", 'Khot', "A hot condition", /* [2] */ "warm", 'Kwrm', "A warm condition", /* [3] */ "cool", 'Kcoo', "A cool condition", /* [4] */ "cold", 'Kfrz', "A cold condition" } },
set the applicability of filter 1 to {incoming, outgoing, ...}
To create a record definition, invent a name for your record type and create a new class in your 'aete' resource with the record type name as the class name. Define all the possible labels as properties. As an example, Listing 5 shows the "class" definition you would create in your 'aete' resource for a record that looks like the following:
{name:"Fred", age:3, status:warm}In this case, you would also define the enumeration for status with the enumerators hot, warm, cool, and cold. The record type would appear in the dictionary as follows:
class person info: A record containing information about a person person info name string -- the name age short integer -- age in years status hot|warm|cool|cold -- current statusSince a record definition is an "abstract class," it should be placed in the Type Definitions suite, described in the next section.
Listing 5. Class definition for our sample record definition
{ /* array Classes: 1 element */ /* [1] */ "person info", 'CPIN', "A record containing information about a person", { /* array Properties: 3 elements */ /* [1] */ "name", 'pnam', 'itxt', "the name", reserved, singleItem, notEnumerated, ... /* [2] */ "age", 'AGE ', 'shor', "age in years", reserved, singleItem, notEnumerated, ... /* [3] */ "status", 'Psta', 'Esta', "current status", reserved, singleItem, enumerated, ... }, { /* array Elements: 0 elements */ }, }
In the case of the record definition classes described in the previous section, you need to define abstract classes that don't refer to real objects. You'll also need to do this in the case of extra classes defined for property inheritance, which aren't instantiable as objects in your application. To include these record or type definitions, create a Type Definitions suite (also known as an Abstract Class suite) with the ID 'tpdf' (kASAbstractClassSuite; note that this constant isn't defined in any .r files, so you'll need to define it yourself) and include your abstract class and record definitions.
On some occasions you may want to add terms to your vocabulary that you don't want to show up in your dictionary. For example, you might need to provide the terms for primitive types, such as integer and point, to make AppleScript work properly, but users are already familiar with these elemental terms and don't need to see them defined. In this case, make a Type Names suite with the ID 'tpnm' (kASTypeNamesSuite) and include your types as classes in this suite. Well-behaved editors such as Apple's Script Editor and Scripter from Main Event will suppress the display of this suite.
To sum up, if you want these definitions to be visible to the user, include them in your Type Definitions suite. If you want them to be hidden, include them in the Type Names suite. Use of these suites will help keep the rest of your suites less cluttered.
Some developers have relied on a default or current target, such that commands that don't include a specific object target will act on the frontmost window or the last explicitly set object. There are three reasons to be careful here:
attach <document-list> to <mail-message-target>The problem is that OpenDoc requires the target to be in the direct parameter. In the preceding script, the target is in the to parameter, not the direct parameter. To make this compatible with OpenDoc, you'll need to change the attach verb to attach to and swap the direct parameter and the to parameter, like this:
attach to <mail-message-target> documents <document-list>
Define the parameter's type as an enumeration instead of an object specifier. Use a # as the first character of the 4-byte ID for the enumeration. Then define the enumerators as the object classes that are appropriate for the event. You can use the same enumeration for more than one event; you can define different enumerations with different sets of object enumerators for different events; and you can even indicate the same object class in more than one enumeration. For example, instead of
close referencea dictionary entry incorporating this technique would read
close window|connection|folderThis entry indicates to the user that the only object classes that make sense for the close command are window, connection, and folder.
It's not always clear which is the better way to go -- object or property. Some examples may help you understand how to decide this. Certain Finder objects have properties but are themselves properties of the application or the desktop container. The selection, an object of the abstract "selection-object" class, has properties such as the selection's contents. However, the selection-object class is never actually used in scripts; selection is listed as a property of the application and other selectable objects, so that a script writer doesn't need to form an object specifier, and the class name can be used as the object itself ("selection" instead of "selection 1").
As another example, a tool palette, which would normally be an object class, might be one of several objects of the palette class, or it might be better listed as a property of the application. This would depend on whether you had several named palettes (palette "Tools," palette "Colors") or wanted separate identifiers for each palette (tool palette, color palette). It could also depend in part on whether there were properties (and perhaps elements) of the palettes. In this particular case, using the tool palette and color palette properties is more localizable than including the name of the palette in the script. If you translate the program into some other language, it's a fair bet that the tool palette won't be named "Tools" anymore. However, your 'aete' resource will have been localized and thus tool palette will be transformed into the correct name for the object.
Try to be careful when deciding whether to make something a property or an object -- users can end up writing
<property> of <property> of <object>or even
<property> of <object> of <property> of <object>and may become confused by real objects that appear to be datalike or that normally would be elements but are presented as properties. Make something a property only when it's meaningful rather than for convenience; otherwise, the concept of an object model hierarchy becomes eroded.
Whether something is a property or an object really depends on the specifics of your application. Still, in a large number of cases, objects are things that can be seen or touched, while properties are characteristics of the objects or the application. A good rule of thumb is: If the item in question is a characteristic of something else, it's probably a property.
As an example, in QuarkXPress, several of the object classes have a large number of properties. Without inheritance, there would have been up to a hundred properties in the dictionary's list of properties for some of the classes! By creating abstract base classes in the 'aete' (defined in the application's Type Definitions suite) and inheriting from these, the application uses the same sets of properties (some quite large) in several different classes. The size of the 'aete' resource was reduced from 67K to 44K, and the lists of properties for many of the classes were reduced to just a few, including the inheritance property.
On the other hand, because this method produces a hierarchy that's smaller but more complex (and therefore slightly more confusing), I recommend using it only in situations where inheritance applies to more than one class. If you plan to use inheritance in only one place in your 'aete', or if your 'aete' isn't particularly large, it's probably better just to repeat all the properties in each class without using inheritance.
By contrast -- and this is very important because it's the single most common source of terminology conflicts -- don't use the same type code for more than one event, or more than one class, and so on. If you do, AppleScript will change the script to show the last event or class defined with that code, changing what the user wrote in the script. This is usually not the desired effect, unless you specifically want synonyms.
If you do want synonyms, you can create them this way. For instance, in HyperCard the term "bkgnd field" is defined before "background field." The former can be typed and will always be transformed into the latter at compile time, so that the latter is always displayed. Just be careful not to have the script appear to change terminology indiscriminately -- it's unsettling to the user.
The section "ID Codes and the Global Name Space" later in this article discusses additional considerations having to do with type codes.
if selected of thing 1 then ...or
tell thing 1 if selected then... end tellis better than writing
if is selected of thing 1 then ...or
tell thing 1 if is selected then ... end tellHowever, it's OK to use has or wants (which have none of the problems presented by is), as in
if has specs then ...or
set wants report to trueWhen you name your Boolean parameters, keep in mind that AppleScript will change true and false to with and without. If the user writes
send message "Fred" queuing trueit compiles to
send message "Fred" with queuing
There are two solutions to this:
analyze <reference> 75 Boolean parameters indicating various analysis optionsIt would be better to split the analysis capability into multiple commands, followed by small groups of Boolean parameters, forming a suite, such as
cluster <reference> small number of Boolean parameters indicating clustering options, or list of enumerators correlate <reference> small number of Boolean parameters indicating correlation options, or list of enumerators fit curve <reference> small number of Boolean parameters indicating curve-fitting options, or list of enumeratorsand so on.
When you return an object specifier as a reply, as in the case of the make command, it's up to you to decide which reference form to use. Reference forms (the various ways objects can be described in a script), also known as keyforms, include the following:
Deciding which reference forms to use for object specifiers comes into play in applications that are recordable, as well.
There are two approaches to recording commands. One approach is to send something as close as possible to what the user would write to the recorder. This isn't necessarily a mirror image of the user's actions but produces recorded statements that more closely resemble what a user will write.
open folder "Goofballs" in disk "Razor"The other approach is to duplicate the actions of users. This is the method used in the Scriptable Finder. In this method, what's recorded is that the user makes a selection and then acts on that selection.
select folder "Goofballs" in disk "Razor" open selectionIn the first case, the recorded statement helps the user understand how to write the command (my personal favorite). In the other case, there's a relationship between what the user did and what was recorded. Either method is useful -- it depends on your objectives.
As is the case with returning object specifiers as replies (discussed above), you decide which reference forms to use for object specifiers that get recorded.
The problem stems from three issues:
It's impossible to completely avoid every term used in every application, but where possible, try not to use terms that are likely to be used by application developers. Remember that a user may load up a computer with any number of osax collections, without realizing that there are four different rename file osaxen among the horde (or should I say herd?).
In addition, remember that if, for example, you define an open file command as an osax, the command
open file "curly"is ambiguous. A user might want the Open event
open (file "curly")or an osax command,
(open file) "curly"Again, be extra careful when defining system-level terms.
A different problem exists in the special case where a set of osaxen is marketed for use with a special application, such as plug-ins or database connectivity. In this case, you should name your commands so that they are unmistakably associated with their host application. One possible solution is to begin the command names with a prefix indicating that they should only be used with the particular application.
Conversely, if you make up a new 4-byte ID code for your own property or enumerator, you need to take reasonable precautions to avoid using a code that corresponds to another term in the global name space. If you don't use a completely new code, you can't be sure which term is represented by that code in scripts that contain the code. So, for example, you shouldn't use the code 'asmo' unless you're referring to the modification date property.
How can you identify potential conflicts? One way is by using a script editor, MacsBug (with the aevt dcmd and the atsend macro), and the templates on the AppleScript Developer CD, notably the templates for the Apple Event Manager traps. Together, these tools enable you to catch an Apple event as it's sent and to examine it. Here's what you do:
Two well-known developers have each recently done a relatively full scripting implementation and have indicated to their users that this is the first version, that some of it is experimental and is likely to change. A number of others have retraced their steps, rethinking their approach, and on occasion switched to object model support. I'll give two examples of applications where changing a scripting implementation made a significant difference.
tell application "Eudora" CreateElement ObjectClass message InsertHere mailfolder "Good stuff" Move message 1 InsertHere mailfolder "Other stuff" end tellThis was an easy cleanup job, involving mostly just changes to the dictionary. Standard human terms were substituted for Apple event constructs, as can be seen in this script that now accomplishes the same thing as the preceding script:
tell application "Eudora" make new message at mail folder "Good stuff" move message 1 to mail folder "Other stuff" end tellYour terms don't have to be quite this far afield for you to consider a scripting facelift.
Here's a synopsis of the original implementation:
Once the object model was implemented, the scheme became a lot simpler:
In the end, though, a unique treatment is usually necessary to fully express the particular capabilities of each application, and your scripting implementation should be carefully constructed accordingly.
I hope this article has convinced you to do the following:
If you do undertake designing a scripting implementation yourself, you'll find it to be a rewarding experience, one that can enable your users to accomplish things never before possible. Happy implementing!
CAL "MR. APPLESCRIPT" SIMONE (AppleLink MAIN.EVENT) has dedicated his life to bringing scripting to the masses. He can usually be found moving fast through the Worldwide Developers Conference or MACWORLD Expo, a cloud of dust in his wake. A founder of Main Event Software of Washington, DC, he designed the Scripter authoring and development environment for
AppleScript and sometimes teaches AppleScript at corporate sites. An honorary member of the Terminology Police as a result of having reviewed scripting vocabularies for more than two dozen third-party products, Cal is available to look at yours. He lives about a mile from the White House and was fond of saying of President Bush, "I don't bother him, and he doesn't bother me."
Thanks to our technical reviewers C. K. Haun, Don Olson, and Jon Pugh, and to Michael Bayer.