Skip to content
hhas edited this page Feb 5, 2023 · 2 revisions

App objects

Creating application objects

Before you can communicate with a scriptable application, you must obtain an app object. For now, use the app command, passing the app’s bundle ID:

app {bundle_identifier as string}

For example:

set Finder to app “com.apple.finder” 

set TextEdit to app “com.apple.textedit”

set Music to app “com.apple.Music”

Targeting applications

Use tell TARGET to ACTION to direct commands to an application:

tell Finder to open home  « open the home folder in a window »

tell TextEdit to do
    activate
    set d to make new: #document at: end of documents
    make new: #paragraph at: end of paragraphs of d with_data: “Hello” 
done

BUG: set…to… isn't delegating correctly when left operand is not a reference (i.e. it should bind the result of make to the name d in the parent scope, the app’s set handler effectively operating as a multimethod that dispatches on its left operand being an app-defined command or local name); alternatively, set could be reserved for external targets only and label:value notation used to bind locally

Unlike AppleScript’s tell statement, which redirects all commands to the target object, iris sends commands which are not handled by the target object to the parent scope as normal.

Tell blocks can be nested, as in AppleScript:

tell Finder to do
    tell home to do
        tell folders to do
            get name
        done
    done
done

Basic commands

All applications should respond to the following commands:

  • run -- Run an application. Most applications will open an empty, untitled window.
  • launch -- Launch an application without sending it a run event. Applications that normally open a new, empty document upon launch won't do so.
  • activate -- Bring the application to the front.
  • reopen -- Reactivate a running application. Some applications will open a new untitled window if no window is open.
  • open {value} -- Open the specified object(s). The value is list of objects to open, typically a list of Path objects.
  • print {value} -- Print the specified object(s). The value is a list of objects to print, typically a list of Path objects.
  • quit {saving: value as choice [#yes, #ask, #no]} -- Quit an application. The saving value indicates whether or not any currently open documents should be saved before quitting.

Some applications may provide their own definitions of some or all of these commands, so check their terminology before use.

aelib also defines get and set commands for any scriptable application that doesn't supply its own definitions:

get { direct_parameter as reference } returning anything

set { direct_parameter as reference, to: value as anything }

Checking if an application is running

You can check if the application specified by an Application object is currently running by getting its is_running property. This is useful if you don't want to perform commands on an application that isn't already running. For example:

tell app “com.apple.textedit” to do
    « Only perform TextEdit-related commands if it's already running: »
    if is_running then do
      « all TextEdit-related code goes here... »
    done
done

Launch errors

If the application can't be launched for some reason (e.g. if it's in the Trash), a "can't launch application" error will be raised. This provides a description of the problem (if it's a standard LaunchServices error) along with the original OS error number.

Object specifiers

Property specifiers

An object property contains one of the following:

  • a simple value (number, string, array, etc.) that describes an attribute of that object, such as its name, class, size, or, color.
  • an object specifier that represents a one-to-one relationship between this object and another object within the application's object model, providing a convenient shortcut to another object of particular interest to users; for example, the Finder's startup_disk and home properties identify the current user's startup disk and home folder, Music's current_track property contains a object specifier that identifies the currently playing track.

Syntax:

property of reference

Examples:

text of document at 1 of TextEdit

current_track of Music

name of every file of home of Finder

Element specifiers

Many objects also have elements, which represent a one-to-many relationship between that object and others.

Object elements often mirror the application's underlying hierarchical data model; for example, the Finder's application object contains one or more disk objects, which can contain any number of file and/or folder objects, which may contain further file and/or folder objects, and so on, just as the file system itself is structured:

files of … folders of disks of finder

At other times, they may represent relationships provided simply as a convenience to scripters, e.g. the Finder's application object also provides file and folder elements as quick shortcuts to file and folder objects on the user's desktop; thus all these object specifiers identify the same file objects (assuming the current user is named jsmith):

files of folder named “Desktop” of folder named “jsmith” of folder named “Users” of startup_disk of Finder

files of folder named “Desktop” of home of Finder

files of desktop of Finder

files of Finder

A element specifier uses the following syntax to identify all of an object's elements by default:

every element of reference

elements of reference

Examples:

folders of home of Finder

windows of TextEdit

every paragraph of every document of TextEdit

Selecting elements

Once you've constructed an object specifier that identifies all of the elements of an object, you can further refine this selection to specify just one, or several, of those elements in particular. To do this, you call one (or sometimes more) of the following reference form methods:

  • at
  • named
  • id
  • first/middle/last/any
  • previous/next
  • from (combined with thru)
  • where
  • beginning, end, before, after

The following sections explain how to use each of these reference forms.

at (by index)

Identifies a single element object by its position.

Syntax:

elements at selector

The selector value is a non-zero integer representing the object's index position. The first element has index 1, the second element's index is 2, the third is 3, and so on. Negative numbers can be used to count backwards from the last element: -1 indicates the last element, -2 the second-to-last, and so on.

Examples:

words at 3
items at -1

Some applications also allow non-integer values to be used. For example, Finder also accepts a File object that identify a file/folder/disk location anywhere in the file system:

app('Finder').items.at( new File('/path/to/some/file') )

named (by name)

Identifies the first element with the given name.

Syntax:

elements named selector

The selector value is a string representing the object's name as given in its name property.

Examples:

disk named “Macintosh HD”
file named “index.html”

Notes:

Applications usually treat object names as case-insensitive.

Where multiple element have the same name, a by-name reference only identifies the first element found with that name. If you wish to identify all elements with the same name, use where instead.

id (by unique identifier)

Identifies a single element using a unique application-supplied key (typically an integer or string).

Syntax:

elements id selector

Examples

window id 4321

first/middle, last, any (by absolute position)

Identify the first, middle, or last element, or a randomly chosen element.

Syntax:

first elements
middle elements
last elements
any elements

Examples:

first document
last paragraph
any file

before/after (by relative position)

Identify a single element before or after (i.e. relative to) this one.

Syntax:

class_name before element
class_name after element

The class_name value is a keyword object indicating the class of the nearest object to select (e.g. document, folder). For example, to specify the next folder after 'Music' in the user's home folder:

folder after folder named “Music” of home of Finder

Examples:

word after word at 3
character before last paragraph

thru (by range)

Identifies all elements between and including the beginning and end of the range.

Syntax:

elements from start thru stop

The start and stop values may be integers, strings, and/or relative or absolute references.

The beginning and end elements are declared relative to the object containing the elements being selected. For example, the following reference indicate the third paragraph of the currrent container object:

paragraph at 3

Thus, to select every paragraph from the third through to the second-to-last:

paragraphs from paragraphs at 3 thru paragraph at -2

When the start and end elements are the same class as the elements being selected, which is frequently the case, thru can be written much more concisely, like this:

paragraphs from 3 thru -1

thru will automatically convert number and string arguments to relative references.

Some applications can understand quite complex range requests. For example, the following will work in Tex-Edit Plus:

words from character at 5 thru paragraph at -2 of first document

Examples:

documents from 1 thru 3
folders from “Downloads” thru “Movies”
text from character at 5 thru word at -2

where (by test)

Identifies zero or more elements whose properties and/or elements match one or more given tests. (In AppleScript, this is commonly known as a 'whose' (or 'where') clause.)

Syntax:

elements where test_reference

The test_reference value is composed of the following:

  • One or more relative references that identify those properties and/or sub-elements to which a filter test should be applied.

  • For each of those references, the conditional test (e.g. is_same_as, is_in) to apply to the value it identifies.

  • If more than one test is being performed, or if a true result should produce false and vice-versa, Boolean-style logic tests (and/or/not) should also be used to combine them into a single compound test references.

Developer note: when evaluating its right operand, the where handler overrides stdlib’s comparison/containment/logic handlers with its own implementations that accept and return relative references.

Comparison tests:

relative_reference is_before value       « is less than »
relative_reference is_not_after value    « is less than or equal to »
relative_reference is_same_as value      « is equal to »
relative_reference is_not_same_as value  « is not equal to »
relative_reference is_after value        « is greater than »
relative_reference is_not_before value   « is greater than or equal to »

TO DO: these operator names may change or be aliased; math operators might also be supported

Containment tests:

relative_reference begins_with value
relative_reference ends_with value
relative_reference contains value
relative_reference is_in value

The following logic tests are supported:

relative_reference and relative_reference
relative_reference or relative_reference
not relative_reference

Important: These are the only operations understood by the where reference form. You cannot construct test clauses using other operators (e.g. +, %) or functions (trim, round), as the Apple Event Object Model does not recognize these and cannot perform them itself.

Examples:

name is_same_as “”
size > 1024
first word begins_with “A”
first character is_same_as last character

it is_not_same_as “”
size > 1000 and size < 100000

word at 1 begins_with “A” or word at 2 contains “ce” or word at 1 is_same_as “Foo”

TO DO: it is not yet implemented

When testing certain classes of element, such as words and paragraphs, you can even apply a containment test directly to it. For example, to specify every word that isn't "Wednesday" within the front TextEdit document:

(words where it is_not_same_as “Wednesday”) of first document of TextEdit

beginning, end, before, after (insertion location)

Unlike other reference forms, which identify properties or elements, the insertion form identifies locations before/after/between an object's current elements.

Syntax:

beginning of elements
end of elements
before element
after element

Examples:

end of documents
before paragraph at 1

Application commands

TO DO: how to pass event attributes (timeout, considering/ignoring, send options)? what about requested type?

To direct commands to an application, use a tell block:

tell APP to COMMAND

tell APP to do
    COMMAND
    ...
done

For example:

tell app “com.apple.music” to quit

### Command syntax

A command optionally accepts a single “arguments” object which contains the command's direct parameter and/or named parameters, plus any standard attributes (sendOptions, withTimeout, ignoring). For example, to set the text of the frontmost TextEdit document:

tell TextEdit to set text of first document to "Hello, World!"

The following close command closes all TextEdit documents, asking the user what to do about any unsaved changes:

tell TextEdit to close every document saving: #ask

Examples

tell TextEdit to activate

tell TextEdit to open paths « TO DO: how to identify fs objects »

tell Finder to get version

tell Finder to set name of file named "foo.txt" of home to "bar.txt"

tell TextEdit to count paragraphs of first document

tell TextEdit to make new: #document at: end of documents with_properties: {name: "README"}

tell Finder to get items of home as: #alias_list « TO DO: `as:` is currently unsupported in default terms and poorly/not supported by most apps »

Command attributes

TO DO: not yet implemented

Whereas an Apple event's parameters contain user data to be passed directly to the application's event handler for processing, its attributes control exactly how the Apple event should be sent and received. While the default values are sufficient in most cases, you can supply alternate values via the command's parameters object if required. The following attribute names are recognized:

  • ignoring - a list of zero or more of the following keywords - k.case, k.diacriticals, k.hyphens, k.punctuation, k.whitespace, k.numericStrings - indicating which text attributes the application should consider or ignore when comparing text values itself. The default is [k.case], i.e. ignore case but consider all other attributes. (Note that most applications ignore text comparison flags and always use the default.)

  • asType - A keyword indicating the type of value the command should return as its result (assuming it knows how to produce values of that type). This attribute is supported by some application's get commands; for example, for example, to tell Finder to return an alias value (Path) instead of a file/folder object specifier, use get({ asType: k.alias }).

  • timeout - The number of seconds NodeAutomation should wait for the application to respond before giving up and reporting a timeout error. To wait forever, use 0. To use the default timeout (2 minutes), use null. [TBC]

  • sendOptions - [TBC]

Syntactic special cases

make command

If a make command does not already have an at parameter then the current (tell) target is used:

insertionSpecifier.make({ new: className })

is shorthand for:

applicationObject.make({ new: className, at: insertionSpecifier })

For example:

app('TextEdit').make({ new: k.document, at: app.documents.end });

is written more neatly as:

app('TextEdit').documents.end.make({ new: k.document });

count command

The count command doesn't exactly replicate AS's own behavior, which automatically adds an each: parameter. Cocoa-based apps are unaffected but some Carbon-based apps (e.g.Finder) will error without it, in which case count some_reference each: #item should be sufficient.

Command errors

TO DO: errors are not yet fully implemented, nor the means to catch them

The CommandError exception describes an error raised by the target application or Apple Event Manager when sending a command.

CommandError(Exception)

    Properties:
      errorNumber : int -- Mac OS error number
      errorMessage : str -- application-supplied/generic error description
      offendingObject : anything | None -- object that caused the error, 
                                           if given by application
      expectedType : anything | None -- object that caused a coercion error, 
                                        if given by application
      partialResult : anything | None -- part of return value constructed 
                                         before error occurred, if given 
                                         by application

  Methods:

      [Symbol.toPrimitive](hint) -- Mac OS error number or formatted description of error

Note to AppleScript users

Unlike AppleScript, which implicitly sends a get command to any unresolved references at the end of evaluating an expression, aelib only resolves a reference when it receives an appropriate command. For example:

tell app “com.apple.textedit” to do
    set d to every document
done

is equivalent to (AppleScript):

tell application id "com.apple.textedit"
    set d to a reference to every document
end tell

In these scripts, the value assigned to d is a reference: every document of «TextEdit».

This is not the same as (AppleScript):

tell application id "com.apple.textedit"
    set d to every document
end tell

Here, AppleScript resolves the reference to every document by implicitly sending TextEdit a get command, which returns a list of references. Whereas, iris only resolves references when explicitly told to do so:

tell app “com.apple.textedit” to do
    set d to get every document
done