8.6. Compiled Script Files as Script ObjectsA script can read a compiled script file and incorporate its contents as a script object. Similarly, a script can save a script object out to disk as a compiled script file. You might use this mechanism as a means of implementing file-level persistence, or to build a separate library of commonly needed code that all scripts can share. The mechanism depends upon three verbs. They're not part of AppleScript proper, but are implemented in a scripting addition (Chapter 3) that's standard on all machines.
Syntaxload script aliasOrFile DescriptionReturns the top-level script of the compiled script file or applet aliasOrFile as a script object. Exampleset myScript to load script alias "myDisk:myFile.scpt"
Syntaxrun script aliasOrFile [with parameters list] DescriptionTells the top-level script of the compiled script file, applet, or text file aliasOrFile to run (compiling first if necessary), optionally handing it the list as the parameters for its explicit run handler, and returns the result. Examplerun script alias "myDisk:myFile.scpt"
Syntaxstore script scriptObject [in aliasOrFile [replacing yes|no]] DescriptionSaves scriptObject to disk as a compiled script file or applet. Returns no value. If no further parameters are supplied, presents a Save File dialog; if the user cancels, a runtime error is raised. If aliasOrFile is supplied, the Save File dialog is suppressed, but if the file exists already, presents a dialog asking how to proceed; if the user cancels, a runtime error is raised. If replacing is supplied, this dialog is suppressed; if yes, the file is just saved, and if no, an error is raised if the file exists. The filename extension determines the format of the resulting file: .scpt (or nothing) for a compiled script file, .scptd for a script bundle, .app for an applet. (The applet will be an applet bundle unless a nonbundle applet already exists.) Examplestore script sayHello in file "myDisk:myFile.scpt" replacing yes (On aliases and file specifiers and the differences between them, see Chapter 13. The verb run script, instead of a file, can take a string, and it then functions as a kind of second level of evaluation; see Chapter 19.) When you save a script object with store script, the lines delimiting the script object definition block (if any) are not included. This fact makes sense, since those lines were never part of the actual script object to begin with. So, for example: script sayHello display dialog "Hello" end script store script sayHello in file "myDisk:myFile.scpt" replacing yes What is saved in myFile.scpt is the single line: display dialog "Hello" 8.6.1. Data StorageRecall from earlier in this chapter that top-level script object entities are persistent, but that at the level of a file on disk, this persistence is unreliable, because it depends upon the environment where the script runs. For example, Entourage's Script menu doesn't save a compiled script file back to disk after execution, so top-level entities don't persist between executions. We can get around this uncertainty by storing needed data ourselves in a separate compiled script file. A script object that we save with store script is saved along with the current values of its top-level entities. Thus we can guarantee file-level persistence by saving a script object as a compiled script file separately from our main script.
Here's an example where we display the user's favorite color. This information will be stored persistently in a file on the desktop called myPrefs.scpt. (The path to command is discussed in Chapter 21.) First we try to load this file. If we succeed, fine; if we fail, we ask for the user's favorite color and store it in myPrefs.scpt. Either way, we now know and can display the user's favorite color. If the user doesn't move myPrefs.scpt, the next time the script runs there will be no need to ask for the user's favorite color; we will already know it. set thePath to (path to desktop as string) & "myPrefs.scpt" script myPrefs property favoriteColor : "" end script try set myPrefs to load script file thePath on error set favoriteColor of myPrefs to text returned of ¬ (display dialog "Favorite Color:" default answer ¬ "" buttons {"OK"} default button "OK") store script myPrefs in file thePath replacing yes end try display dialog "Your favorite color is " & favoriteColor of myPrefs You might object that a file sitting on the user's desktop is a silly place to store our data. You're perfectly right; it's only an example! How might we solve this problem in real life? We need a place to put the data where the user won't see it or object to it. One solution, if it is supported by the environment where the script will run, would be to make this script a script bundle and store the secondary script inside the bundle. A script bundle looks like a file, so the secondary script is out of sight inside it. The first line of the script could then be something like this: set thePath to (path to me as string) & "Contents:Resources:myPrefs.scpt" Unfortunately, a script runner environment that doesn't implement persistence might not know about script bundles either. Perhaps a better place might be the user's Preferences folder: set thePath to (path to preferences as string) & "myPrefs.scpt" If you open the file myPrefs.scpt in a script editor application, you may be surprised to find that it doesn't actually seem to contain your favorite color: property favoriteColor : "" Don't worry! The decompiled version of the script, which is what you're seeing, shows the original bytecode, not the persistent data stored internally with the script. But the persistent data is there. (The only way I know of to get a look at the persistent data stored internally with a script is to use Script Debugger. Script Editor doesn't show it, and destroys it if you open and run the script directly.) 8.6.2. LibraryA compiled script file may be used to hold commonly needed routines. A running script can access the contents of the script file using load script. The script file's top-level entities, including its run handler, are then available to the running script that loads it. A compiled script file used in this way is called a library . For example, AppleScript has no native command to remove one item from a list (that is, to return a list without its nth item). Let's write one. Ooooh, we've already done it; see "LISP-likeness" in Chapter 4. Save that script (the AppleScript version, not the LISP version); for now, let's save it as library.scpt on the desktop. Here's how to use the library:
-- load the library...
set f to (path to desktop as string) & "library.scpt"
set lib to load script alias f
-- . . . and use it
set L to {"Mannie", "Moe", "Jack"}
set L2 to lib's remvix(2, L)
L2 -- {"Mannie", "Jack"}
What are the advantages and disadvantages of using a library ? An obvious advantage is that it makes code reusable and maintainable. Here, remvix is a useful handler. We don't want to have to keep copying and pasting it into different scripts. If its code lives in a library, it becomes accessible to any script we write. Furthermore, if we improve remvix in its library file, those improvements are accessible to any script; a script that already calls remvix by way of load script acquires any new improvements the next time it runs. Finally, a library may consist of many interrelated handlers that call each other (and may even share values by way of top-level properties). When you load the library, you load that entire functionality and the handlers work properly without your having to worry about the complexities of dependency. A disadvantage is that a library reduces portability. We cannot just copy a script that calls remvix to another machine, or send it to a friend, because it depends on another file, library.scpt, and refers to it by a pathname that won't work on any other machine. With Script Debugger, a trick for working around this problem is to load any library files as part of your script property initialization:
-- load the library...
property f : (path to desktop as string) & "library.scpt"
property lib : load script alias f
-- . . . and use it
set L to {"Mannie", "Moe", "Jack"}
set L2 to lib's remvix(2, L)
L2 -- {"Mannie", "Jack"}
This works because, as explained earlier in this chapter, Script Debugger saves top-level entities into the compiled script file. So when you run this script and then save it as a compiled script file with Script Debugger, the property lib is saved in its initialized state, meaning that it contains the script object loaded from disk. The resulting compiled script file thus contains the library that it uses, and will run correctly in a script runner on another machine. In fact, this compiled script file can even be opened and executed using Script Debugger on another machine. However, if you open this script with Script Editor, the script is ruined, because Script Editor strips out the saved top-level entities when it opens a file. Furthermore, if you edit this script on another machine, the script is ruined. At that point, the values of the script properties will be thrown away, AppleScript will try to reinitialize them, the load script command will fail because the file it refers to doesn't exist, and the script will no longer compile or run. Thus, this trick is probably most appropriate when you can afford to distribute a script as run-only. (Actually, Script Debugger helps you further in this situation as well. It lets you "flatten" a script so that it incorporates all library files on which it depends, and so has no load script dependencies.) An interesting attempt to rationalize the library mechanism is the freeware utility Loader . The idea is to try to make it as easy to install and take advantage of libraries, even if they involve mutual dependencies, as in languages like Perl and Python. I haven't tried this myself, but it looks intriguing. |