py-appscript manual | 3. Quick tutorial

#

## 3. A quick tutorial

The following tutorial provides a practical taste of application scripting with appscript. Later chapters cover the technical details of appscript usage that are mostly skimmed over here.

### 'Hello World' tutorial

This tutorial uses appscript, TextEdit and the interactive command line `python` interpreter to perform a simple 'Hello World' exercise.

Caution: It is recommended that you do not have any other documents open in TextEdit during this tutorial, as accidental modifications are easy to make and changes to existing documents are not undoable.

To begin, launch Terminal.app and type `python` followed by a newline to launch the `python` interpreter:

    brian% python
    Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43)
    [Clang 6.0 (clang-600.0.57)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>>

#### Target TextEdit

The first step is to import the appscript module. Appscript exports only a few public attributes - `app`, `k`, `con`, `its`, `CommandError` and `ApplicationNotFoundError` - so it's common practice to import these into the top-level namespace for convenience:

    >>> from appscript import *
    >>>

Once appscript is imported, create new `app` object, identifying the application to be manipulated, and assign it to a variable, `te`, for easy reuse:

    >>> te = app('TextEdit')
    >>>

The application may be identified by name, path, bundle ID, or, if running remotely, URL. If the application is local and is not already running, it will be launched automatically for you.

#### Create a new document

First, create a new TextEdit document by making a new `document` object. This is done using the `make` command, passing it a single parameter, `new=k.document`, indicating the type of object to create:

    >>> te.make(new=k.document)
    app('/System/Applications/TextEdit.app').documents[1]
    >>>

Because `document` objects are always elements of the root `application` class, applications such as TextEdit can usually infer the location at which the new `document` object should appear. At other times, you need to supply an `at` parameter that indicates the desired location.

As you can see, the `make` command returns a reference identifying the newly-created object. This reference can be assigned to a variable for easy reuse. Use the `make` command to create another document, this time assigning its result to a variable, `doc`:

    >>> doc = te.make(new=k.document)
    >>>

#### Set the document's content

The next step is to set the document's content to the string `"Hello World"`. Every TextEdit document has a property, `text`, that represents the entire text of the document. This property is both readable and writeable, allowing you to retrieve and/or modify the document's textual content as unstyled unicode text.

Setting a property's value is done using the `set()` command. The `set()` command is exposed as a method of the root `application` class and has two parameters: a direct (positional) parameter containing reference to the property (or properties) to be modified, and a keyword parameter, `to`, containing the new value. In this case, the direct parameter is a reference to the new document's `text` property, `doc.text`, and the `to` parameter is the string `"Hello World"`:

    >>> te.set(doc.text, to='Hello World')
    >>>

The front TextEdit document should now contain the text '`Hello World`'.

Because the above expression is a bit unwieldy to write, appscript allows it to be written in a more elegant OO-like format as a special case, where the `set()` command is called upon the reference and the `to` keyword is omitted:

    doc.text.set('Hello World')

Appscript converts this second form to the first form internally, so the end result is exactly the same. Appscript supports several such special cases, and these are described in the chapter on Application Commands. Using these special cases produces more elegant, readable source code, and is recommended.

#### Get the document's content

Retrieving the document's text is done using the `get()` command:

    >>> doc.text.get()
    'Hello World'
    >>>

This may seem counter-intuitive if you're used to dealing with AppleScript or object-oriented Python references, where evaluating a literal reference returns the value identified by that reference. However, always remember that appscript 'references' are really first-class query objects: while the syntax may look familiar, any similarity is purely superficial. For example, when evaluating the literal reference:

    te.documents[1].text

the result is another reference, `app('/System/Applications/TextEdit.app').documents[1].text`, not the value being referenced (`'Hello World'`). To get the value being referenced, you have to pass the reference as the direct argument to TextEdit's `get()` command:

    >>> te.get(doc.text)
    'Hello World!'
    >>>

As usual, appscript provides alternative convenience forms that allow this to be written as:

    doc.text.get()

or even:

    doc.text()

Depending on what sort of attribute(s) the reference identifies, `get()` may return a primitive value (number, string, list, dict, etc.), or it may return another reference, or list of references, e.g.:

    >>> doc.text.get()
    'Hello World!'
    >>> te.documents[1].get()
    app('/System/Applications/TextEdit.app').documents[1]
    >>> te.documents.get()
    [app('/System/Applications/TextEdit.app').documents[1],
        app('/System/Applications/TextEdit.app').documents[2]]
    >>> te.documents.text.get()
    ['Hello World', '']
    >>>

#### More on `make()`

The above exercise uses two commands to create a new TextEdit document containing the text '`Hello World`'. It is also possible to perform both operations using the `make()` command alone by passing the value for the new document's `text` property via the `make()` command's optional `with_properties` parameter:

    >>> te.make(new=k.document, with_properties={k.text: 'Hello World'})
    app('/System/Applications/TextEdit.app').documents[1]
    >>>

Incidentally, you might note that every time the `make()` command is used, it returns a reference to document 1. TextEdit identifies `document` objects according to the stacking order of their windows, with document 1 being frontmost. When the window stacking order changes, whether as a result of a script command or GUI-based interaction, so does the order of their corresponding `document` objects. This means that a previously created reference such as `app('/System/Applications/TextEdit.app').documents[1]` may now identify a different `document` object to before! Some applications prefer to return references that identify objects by name or unique ID rather than index to reduce or eliminate the potential for confusion, but it's an issue you should be aware of, particularly with long-running scripts where there is greater opportunity for unexpected third-party interactions to throw a spanner in the works.

#### More on manipulating `text`

In addition to getting and setting a document's entire text by applying `get()` and `set()` commands to `text` property, it's also possible to manipulate selected sections of a document's text directly. TextEdit's `text` property contains a `text` object, which in turn has `character`, `word` and `paragraph` elements, all of which can be manipulated using a variety of commands - `get()`, `set()`, `make()`, `move`, `delete`, etc. For example, to set the size of the first character of every paragraph of the front document to 24pt:

    te.documents[1].text.paragraphs.size.set(24)

Or to insert a new paragraph at the end of the document:

    te.make(
            new=k.paragraph,
            with_data='Hello Again, World\n',
            at=te.documents[1].text.paragraphs.end)