diff --git a/Mac/Demo/applescript.html b/Mac/Demo/applescript.html index 183d66599a2..2a27a8a7dbe 100644 --- a/Mac/Demo/applescript.html +++ b/Mac/Demo/applescript.html @@ -1,207 +1,364 @@ -
OSA support in Python is still not 100% complete, but there is already enough in place to allow you to do some nifty things -to other programs from your python program.
+with other programs from your python program.
- -Actually, when we say "AppleScript" in this document we actually mean -"the Open Scripting Architecture", there is nothing -AppleScript-specific in the Python implementation.-
+In this example, we will look at a scriptable application, extract its -"AppleScript Dictionary" and generate a Python interface module from -that and use that module to control the application. Because we want -to concentrate on the OSA details we don't bother with a real -user-interface for our application.
- +“AppleScript Dictionary,” generate a Python interface package from +the dictionary, and use that package to control the application. The application we are going to script is Disk Copy, Apple's standard utility for making copies of floppies, creating files that are mountable -as disk images, etc.
+as disk images, etc. +Because we want +to concentrate on the OSA details, we won’t bother with a real +user-interface for our application.
-+When we say “AppleScript” in this document we actually mean +“the Open Scripting Architecture.” There is nothing +AppleScript-specific in the Python implementation. Most of this document +focuses on the classic Mac OS; Mac OS X users have some +additional tools. +
+ +Open Scripting suites and inheritance can be modelled rather nicely +with Python packages, so we generate +a package for each application we want to script. Each suite defined in +the application becomes a module in the package, and the package main module imports everything from all the -submodules and glues all the classes (Python terminology, OSA terminology is -events, AppleScript terminology is verbs) together.
+submodules and glues together all the classes (in Python terminology— +events in OSA terminology or verbs in AppleScript terminology).
+A suite in an OSA application can extend the functionality of a standard -suite, and this is implemented in Python by importing everything from the -module that implements the standard suite and overriding anything that has -been extended. The standard suites live in the StdSuite package.
+suite. This is implemented in Python by importing everything from the +module that implements the standard suites and overriding anything that has +been extended. The standard suites live in the StdSuite package.
-This all sounds complicated, and you can do strange and wondrous things -with it once you fully understand it, but the good news is that simple -scripting is actually pretty simple.+
+This all sounds complicated, but the good news is that basic +scripting is actually pretty simple. You can do strange and wondrous things +with OSA scripting once you fully understand it.
-gensuitemodule.py
, and lives in Mac:scripts
.
-When we start it, it asks us for an input file and we point it to the
-Disk Copy executable. -Next it wants a folder where it will store the package it is going to generate. +
There is a tool in the standard distribution that can automatically
+generate the interface packages. This tool is called
+gensuitemodule.py
, and lives in Mac:scripts
.
+It looks through a file
+for an ‘AETE’ or ‘AEUT’ resource,
+the internal representation of the
+AppleScript dictionary, and parses the resource to generate the suite
+modules.
+When we start gensuitemodule
, it asks us for an input file;
+for our example,
+we point it to the Disk Copy executable.
+Next, gensuitemodule
wants a folder where it will store the
+package it is going to generate.
Note that this is the package folder, not the parent folder, so we
navigate to Python:Mac:Demo:applescript
, create a folder
-Disk_Copy
and select that.
+Disk_Copy
, and select that.
Python:Mac:Lib:lib-scriptpackages
. (There is
+
+We next specify the folder from which gensuitemodule
+should import the standard suites. Here,
+we always select Python:Mac:Lib:lib-scriptpackages:StdSuites
. (There is
one exception to this rule: when you are generating StdSuites
itself
-you select cancel
, for obvious reasons).
+
+you select _builtinSuites
.)
+
It starts parsing the AETE resource, and for
-each AppleEvent suite it finds it prompts us for the filename of the
+each AppleEvent suite it finds, gensuitemodule.py
+prompts us for the filename of the
resulting python module. Remember to change folders for the first
-module, you don't want to clutter up the Eudora folder with your python
-interfaces. If you want to skip a suite you press cancel and the process
-continues with the next suite.
+module—you don't want to clutter up, say, the
+Eudora folder
+
+with your python
+interfaces. If you want to skip a suite, press cancel
and the process
+continues with the next suite.
gensuitemodule
.Python:Mac:Lib:lib-scriptpackages:StdSuites
+
+ to import the standard suites (or _builtinSuites
if you are
+ generating StdSuites
itself). cancel
to skip a suite).gensuitemodule
does not handle all Python reserved words, so
+ if
+ one of the AppleScript verbs is a Python reserved word, a SyntaxError
+ may be raised when the package is imported.
+ Simply rename the class into something acceptable, if this happens;
+ take a look at how the
+ print
verb is handled (automatically by gensuitemodule
)
+ in the standard suites. System Folder:Extensions:Scripting
+Additions:Dialects:English Dialect
. For newer versions, you will
+find them in System Folder:Extensions:Applescript
.
+Gensuitemodule.py
may ask you questions
+like “Where is enum 'xyz ' declared?”.
+This is either due to a misunderstanding on my part or (rather too commonly)
bugs in the AETE resources. Pressing cancel
is usually the
-right option, it will cause the specific enum not to be treated as an enum
-but as a "normal" type. As things like fsspecs and TEXT strings clearly are
-not enumerators this is correct. If someone understands what is really going on
-here please let me know. +right choice: it will cause the specific enum not to be treated as an enum +but as a “normal” type. As things like fsspecs and TEXT strings clearly are +not enumerators, this is correct. If someone understands what is really going on +here, please let me know.
-Time for a sidebar. If you want to re-create the StdSuite modules -you should look in one of two places. On older systems you will find the -AEUT resources in+System Folder:Extensions:Scripting -Additions:Dialects:English Dialect
. On newer systems you will -find them inSystem Folder:Extensions:Applescript
.-
__init__.py
-and the only interesting bit is the Disk_Copy
class, which
+
+
++Let’s glance at the +Disk_Copy package just created. You +may want to open Script Editor alongside to see how it +interprets the dictionary. +
+ + +
+The main package module is in __init__.py
.
+The only interesting bit is the Disk_Copy
class, which
includes the event handling classes from the individual suites. It also
inherits aetools.TalkTo
, which is a base class that handles all
details on how to start the program and talk to it, and a class variable
_signature
which is the default application this class will talk
-to (you can override this in various when you instantiate your class, see
+to (you can override this in various ways when you instantiate your class, see
aetools.py
for details).
-
-
-Let us do another sidebar. Since MacPython 2.0 this new structure, with packages
-per application and submodules per suite, is used. Older MacPythons had a
-single level of modules, with uncertain semantics. With the new structure
-it is possible for programs to override standard suites, as programs often do.
-It is a good idea to convert your own old programs to the new scheme, but if you
-really want the old standard suites are still available in
-:Mac:Lib:lib-scripting
.
-
+
+
The Special_Events
module is a nice example of a suite module.
-The Special_Events_Events
class is the bulk of the code
-generated. For each verb it contains a method. Each method knows what
-arguments the verb expects, and it makes handy use of keyword
+The Special_Events_Events
class is the bulk of the code
+generated. For each verb, it contains a method. Each method knows what
+arguments the verb expects, and it makes use of keyword
arguments to present a palatable
-interface to the python programmer. You will see that each method
-calls some routines from aetools
, an auxiliary module
-living in Lib:toolbox
which contains some other nifty
-AppleEvent tools as well. Have a look at it sometime, there is (of
-course) no documentation yet.
+interface to the python programmer.
-The other thing you notice is that each method calls
-self.send
, this comes from the aetools.TalkTo
baseclass.
+Notice that each method
+calls some routines from aetools
, an auxiliary module
+living in Mac:Lib
.
+The other thing to notice is that each method calls
+self.send
. This comes from the aetools.TalkTo
+baseclass.
+After the big class, there are a number of little class declarations. These
+declarations are for the (AppleEvent) classes and properties in the suite.
They allow you to create object IDs, which can then be passed to the verbs.
-For instance, to get the name of the sender of the first message in mailbox
-inbox you would use mailbox("inbox").message(1).sender
. It is
+For instance,
+
+when scripting the popular email program Eudora,
+you would use mailbox("inbox").message(1).sender
+to get the name of the sender of the first message in mailbox
+inbox. It is
also possible to specify this as sender(message(1, mailbox("inbox")))
,
-which is sometimes needed because these classes don't always inherit correctly
-from baseclasses, so you may have to use a class or property from another suite.
- -
-There are also some older object specifiers for standard objects in aetools. -You use these in the form+which is sometimes needed because these classes don’t always inherit correctly +from baseclasses, so you may have to use a class or property from another +suite. +aetools.Word(10, -aetools.Document(1))
where the corresponding AppleScript -terminology would beword 10 of the first -document
. Examine the two modules mentioned above along with -the comments at the end of your suite module if you need to create -more than the standard object specifiers. -
Next we get the enumeration dictionaries, which allow you to pass
english names as arguments to verbs, so you don't have to bother with the 4-letter
type code. So, you can say
-
-as it is called in Script Editor, in stead of the cryptic lowlevel
-
+
diskcopy.create(..., filesystem="Mac OS Standard")
-
+as it is called in Script Editor, instead of the cryptic lowlevel
+
+
diskcopy.create(..., filesystem="Fhfs")
-
+
-Finally, we get the "table of contents" of the module, listing all classes and such -by code, which is used by gensuitemodule.+
+Finally, we get the “table of contents” of the module, listing all
+classes and such
+by code, which is used by gensuitemodule
.
+
+
aetools
module contains some other nifty
+AppleEvent tools as well. Have a look at it sometime, there is (of
+course) no documentation yet.
+aetools.Word(10,
+aetools.Document(1))
, where the corresponding AppleScript
+terminology would be word 10 of the first
+document
. Examine
+aetools
and aetools.TalkTo
+along with
+the comments at the end of your suite module if you need to create
+more than the standard object specifiers.
+
+Now that we have created the suite module, we can use it in a Python script.
In older MacPython distributions this used to be a rather
complicated affair, but with the package scheme and with the application signature
known by the package it is very simple: you import the package and instantiate
-the class, as
-
-You will usually specify the start=1: it will run the application if it is
-not already running. You may want to omit it if you want to talk to the application
-only if it is already running, or if the application is something like the Finder.
+the class, e.g.
+
talker = Disk_Copy.Disk_Copy(start=1)
-
+
+You will usually specify the start=1
: it will run the application if it is
+not already running.
+You may want to omit it if you want to talk to the application
+only if it is already running, or if the application is something like the Finder.
+Another way to ensure that the application is running is to call talker.start()
.
+
+Looking at the sourcefile makedisk.py, we see that it starts +with some imports. Naturally, one of these is the Python interface to Disk +Copy.
-The main program itself is a wonder of simplicity. We create the -object that talks to Disk Copy, creates a disk and mounts it.+
+The main program itself is a wonder of simplicity: we create the
+object (talker
) that talks to Disk Copy,
+create a disk, and mount it. The bulk of
+the work is done by talker
and the Python interface package we
+just created.
MacOS.Error
is raised for
-all of the errors that are known to be OSErr
-type errors,
-server generated errors raise aetools.Error
. +
+The exception handling does warrant a few comments, though. Since
+AppleScript is basically a connectionless RPC protocol,
+nothing happens
+when we create the talker
object. Hence, if the destination application
+is not running, we will not notice until we send our first
+command (avoid this as described above). There is another thing to note about errors returned by
+AppleScript calls: MacOS.Error
is raised for
+all of the errors that are known to be OSErr
-type errors,
+while
+server generated errors raise aetools.Error
.
If you want to use any of the scripting additions (or OSAXen, in
-everyday speech) from a Python program you can use the same method
-as for applications, i.e. run gensuitemodule
on the
-OSAX (commonly found in System Folder:Extensions:Scripting Additions
+everyday speech) from a Python program, you can use the same method
+as for applications, i.e. run gensuitemodule
on the
+OSAX (commonly found in System Folder:Scripting Additions
or something similar). There is one minor gotcha: the application
-signature to use is 'MACS'
.
+signature to use is MACS
. You will need to edit the main class
+in the __init__.py
file of the created package and change the value
+of _signature
to MACS
.
+
System_Object_Suite
,
+
+There are two minor points to watch out for when using gensuitemodule
+on OSAXen: they appear all to define the class System_Object_Suite
,
and a lot of them have the command set in multiple dialects. You have to
-watch out for name conflicts, so, and make sure you select a reasonable dialect
-(some of the non-english dialects cause gensuitemodule to generate incorrect
-Python code).
+watch out for name conflicts and make sure you select a reasonable dialect
+(some of the non-English dialects cause gensuitemodule
to generate incorrect
+Python code).
+If you want to look at more involved examples of applescripting, look at the standard
modules findertools
and nsremote
, or (possibly better, as it
-is more involved) fullbuild
from the Mac:scripts folder.
+is more involved) fullbuild
from the Mac:scripts
folder.
+
+Under Mac OS X, the above still works, but with some new difficulties.
+The application package structure can hide the ‘AETE’ or ‘AEUT’ resource
+from gensuitemodule
, so that, for example, it cannot generate an OSA interface to
+iTunes.
+
+One alternative is available through the Unix command line version of python.
+Apple has provided the osacompile
and osascript
tools,
+which can be used to compile and execute scripts written in OSA languages. See the
+man pages for more details.
+