diff options
Diffstat (limited to 'Mac/Demo/applescript.html')
-rw-r--r-- | Mac/Demo/applescript.html | 185 |
1 files changed, 87 insertions, 98 deletions
diff --git a/Mac/Demo/applescript.html b/Mac/Demo/applescript.html index f527b52..8a26a9b 100644 --- a/Mac/Demo/applescript.html +++ b/Mac/Demo/applescript.html @@ -3,8 +3,7 @@ <H1>Using Open Scripting Extension from Python</H1> <HR> -OSA support in Python is still far from complete, and what -support there is is likely to change in the forseeable future. Still, +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. <P> @@ -20,11 +19,27 @@ 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. <p> -The application we are going to script is Eudora Light, a free mail -program from <A HREF="http://www.qualcomm.com">QualComm</A>. This is a -very versatile mail-reader, and QualComm has an accompanying -commercial version once your needs outgrow Eudora Light. Our program -will tell Eudora to send queued mail, retrieve mail or quit. <p> +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. <p> + +<H2>Python OSA architecture</H2> + +Open Scripting suites and inheritance can be modelled rather nicely with +with Python packages, so for each application we want to script we generate +a package. 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. <p> + +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. <p> + +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. <p> <H2>Creating the Python interface module</H2> @@ -33,51 +48,60 @@ for an 'AETE' or 'AEUT' resource, the internal representation of the AppleScript dictionary. This tool is called <CODE>gensuitemodule.py</CODE>, and lives in <CODE>Mac:scripts</CODE>. When we start it, it asks us for an input file and we point it to the -Eudora Light executable. It starts parsing the AETE resource, and for +Disk Copy executable. <p> + +Next it 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 <code>Python:Mac:Demo:applescript</code>, create a folder +<code>Disk_Copy</code> and select that. <p> + +Next it wants the folder from which it should import the standard suites. Here +you always select <code>Python:Mac:Lib:lib-scriptpackages</code>. (There is +one exception to this rule: when you are generating <code>StdSuites</code> itself +you select <code>cancel</code>, for obvious reasons). <p> + +It starts parsing the AETE resource, and for each AppleEvent suite it finds it 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. In the case of Eudora, you do -<EM>not</EM> want to generate the Required and Standard suites, because -they are identical to the standard ones which are pregenerated (and -empty in the eudora binary). AppleScript understands that an empty suite -means "incorporate the whole standard suite by this name", -gensuitemodule does not currently understand this. Creating the empty -<CODE>Required_Suite.py</CODE> would hide the correct module of that -name from our application. <p> +continues with the next suite. <p> Gensuitemodule may ask you questions like "Where is enum 'xyz ' declared?". -For the first time, cancel out of this dialog after taking down the -enum (or class or prop) name. After you've created all the suites look -for these codes, in the suites generated here and in the standard suites. -If you've found them all run gensuitemodule again and point it to the right -file for each declaration. Gensuitemodule will generate the imports to make the -reference work. <p> +This is either due to a misunderstanding on my part or (rather too common) +bugs in the AETE resources. Pressing <code>cancel</code> 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. <p> <BLOCKQUOTE> -Time for a sidebar. If you want to re-create -<CODE>Required_Suite.py</CODE> or one of the other standard modules -you should look in <CODE>System Folder:Extensions:Scripting -Additions:Dialects:English Dialect</CODE>, that is where the core -AppleEvent dictionaries live. Also, if you are looking for the -<CODE>Finder_Suite</CODE> interface: don't look in the finder (it has -an old System 7.0 scripting suite), look at the extension <CODE>Finder -Scripting Extension</CODE>. <p> +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 <CODE>System Folder:Extensions:Scripting +Additions:Dialects:English Dialect</CODE>. On newer systems you will +find them in <code>System Folder:Extensions:Applescript</code>. <p> </BLOCKQUOTE> Let's glance at the <A -HREF="scripting/Eudora_Suite.py">Eudora_Suite.py</A> just created. You +HREF="applescript/Disk_Copy">Disk_Copy</A> package just created. You may want to open Script Editor alongside, and have a look at how it -interprets the dictionary. EudoraSuite.py starts with some -boilerplate, then a big class definition with methods for each -AppleScript Verb, then some small class definitions and then some dictionary -initializations. <p> - -The <CODE>Eudora_Suite</CODE> class is the bulk of the code +interprets the dictionary. The main package module is in <code>__init__.py</code> +and the only interesting bit is the <code>Disk_Copy</code> class, which +includes the event handling classes from the individual suites. It also +inherits <code>aetools.TalkTo</code>, which is a base class that handles all +details on how to start the program and talk to it, and a class variable +<code>_signature</code> which is the default application this class will talk +to (you can override this in various when you instantiate your class, see +<code>aetools.py</code> for details). + <p> + +The <a href="applescript/Disk_Copy/Special_Events.py">Special_Events</a> +module is a nice example of a suite module. +The <CODE>Special_Events_Events</CODE> 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 the keyword -argument scheme introduced in Python 1.3 to present a palatable +arguments the verb expects, and it makes handy use of keyword +arguments to present a palatable interface to the python programmer. You will see that each method calls some routines from <CODE>aetools</CODE>, an auxiliary module living in <CODE>Lib:toolbox</CODE> which contains some other nifty @@ -85,9 +109,7 @@ AppleEvent tools as well. Have a look at it sometime, there is (of course) no documentation yet. <p> The other thing you notice is that each method calls -<CODE>self.send</CODE>, but no such method is defined. You will have -to provide it by subclassing or multiple inheritance, as we shall see -later. <p> +<CODE>self.send</CODE>, this comes from the <code>aetools.TalkTo</code> baseclass. <p> After the big class we get a number of little class declarations. These declarations are for the (appleevent) classes and properties in the suite. @@ -95,7 +117,7 @@ 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 <code>mailbox("inbox").message(1).sender</code>. It is also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>, -which is sometimes needed because these classes don't inherit correctly +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. <p> <blockquote> @@ -112,11 +134,11 @@ 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 <CODE><PRE> - eudora.notice(occurrence="mail_arrives") + diskcopy.create(..., filesystem="Mac OS Standard") </PRE></CODE> -instead of the rather more cryptic +as it is called in Script Editor, in stead of the cryptic lowlevel <CODE><PRE> - eudora.notice(occurrence="wArv") + diskcopy.create(..., filesystem="Fhfs") </PRE></CODE><p> Finally, we get the "table of contents" of the module, listing all classes and such @@ -124,54 +146,25 @@ by code, which is used by gensuitemodule. <p> <H2>Using a Python suite module</H2> -Now that we have created the suite module we can use it in an -application. We do this by creating a class that inherits -<CODE>Eudora_Suite</CODE> and the <CODE>TalkTo</CODE> class from -<CODE>aetools</CODE>. The <CODE>TalkTo</CODE> class is basically a -container for the <CODE>send</CODE> method used by the methods from -the suite classes. <p> - -Actually, our class will also inherit <CODE>Required_Suite</CODE>, -because we also need functionality from that suite: the quit -command. Gensuitemodule could have created this completely derived -class for us, since it has access to all information needed to build -the class but unfortunately it does not do so at the moment. All in -all, the heart of our program looks like this: +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 <CODE><PRE> - import Eudora_Suite, Required_Suite, aetools - - class Eudora(Eudora_Suite.Eudora_Suite, Required_Suite.Required_Suite, \ - aetools.TalkTo): - pass + talker = Disk_Copy.Disk_Copy(start=1) </PRE></CODE> - -Yes, our class body is <CODE>pass</CODE>, all functionality is already -provided by the base classes, the only thing we have to do is glue it -together in the right way. <p> +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. <p> Looking at the sourcefile <A -HREF="scripting/testeudora.py">testeudora.py</A> we see that it starts -with some imports. Then we get the class definition -for our main object and a constant giving the signature of Eudora. <p> - -This, again, needs a little explanation. There are various ways to -describe to AppleScript which program we want to talk to, but the -easiest one to use (from Python, at least) is creator -signature. Application name would be much nicer, but Python currently -does not have a module that interfaces to the Finder database (which -would allow us to map names to signatures). The other alternative, -<CODE>ChooseApplication</CODE> from the program-to-program toolbox, is -also not available from Python at the moment. <p> - -If you specify the application by creator you can specify an optional -<CODE>start</CODE> parameter, which will cause the application to be -started if it is not running. <P> +HREF="applescript/makedisk.py">makedisk.py</A> we see that it starts +with some imports. The main program itself is a wonder of simplicity. We create the -object that talks to Eudora (passing the signature as argument), ask -the user what she wants and call the appropriate method of the talker -object. The use of keyword arguments with the same names as used by -AppleScript make passing the parameters a breeze. <p> +object that talks to Disk Copy, creates a disk and mounts it. <p> The exception handling does need a few comments, though. Since AppleScript is basically a connectionless RPC protocol nothing happens @@ -188,8 +181,7 @@ 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 <CODE>gensuitemodule</CODE> on the OSAX (commonly found in <CODE>System Folder:Extensions:Scripting Additions</CODE> -or something similar), define a class which inherits the generated -class and <CODE>aetools.TalkTo</CODE> and instantiate it. The application +or something similar). There is one minor gotcha: the application signature to use is <CODE>'MACS'</CODE>. <P> There are two minor points to watch out for when using gensuitemodule @@ -199,11 +191,8 @@ 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). <P> -That concludes our simple example. Again, let me emphasize that -scripting support in Python is not very complete at the moment, and -the details of how to use AppleEvents will definitely change in the -near future. This will not only fix all the ideosyncracies noted in -this document but also break existing programs, since the current -suite organization will have to change to fix some of the problems. -Still, if you want to experiment with AppleEvents right now: go ahead! -<p> +<H2>Further Reading</H2> + +If you want to look at more involved examples of applescripting look at the standard +modules <code>findertools</code> and <code>nsremote</code>, or (possibly better, as it +is more involved) <code>fullbuild</code> from the Mac:scripts folder. |