Frontier Guide
Volume Number: 9
Issue Number: 8
Column Tag: Scripting
Related Info: Apple Event Mgr
A Nerdy Guide to Frontier
A top-level technical overview to the Macintosh’s first scripting
system.
By Dave Winer, UserLand Software
Note: Source code files and Runtime Frontier accompanying article are located on
MacTech CD-ROM or source code disks.
About the author
Dave Winer is founder and president of UserLand Software. He and Doug Baron
are the lead developers at UserLand. Dave has been a commercial software developer
since 1979, and shipped his first product, the ThinkTank outliner for the Apple II in
1983. The IBM PC version shipped in 1984. As founder and president of Living
Videotext, Inc. he shipped one of the first Macintosh applications, ThinkTank 128, in
mid-1984, and in 1986 shipped the award-winning MORE 1.0, followed by MORE
1.1c in 1987. Living Videotext merged with Symantec shortly after that and Dave
moved on to start the development of Frontier. Frontier 2.0 received MacUser’s Eddy
award for best development tool of 1992.
Frontier
This article is a top-level technical overview of the UserLand Frontier scripting
system, written for experienced C or Pascal programmers. The goal is to show how the
language and the environment work, but not to be a complete tutorial in using Frontier.
Frontier is an integrated collection of development tools built around a scripting
language and disk-based storage system. Frontier provides a script editor/debugger,
table editor, menubar editor, documentation tools and a comprehensive set of built-in
verbs that allow you to customize and automate the Macintosh file system, operating
system, networks, utilities and scriptable applications.
Frontier scripts can be saved to the Finder desktop, can be linked to menu items,
and can run in the background. Scripts can also be embedded in a small application to
allow collections of files, folders and disks to be dropped onto the script.
Frontier 1.0 shipped in January 1992; version 2.0 shipped in October 1992.
All the examples in this article work with Frontier 2.0.
In the following sections we break out each of the major features in Frontier and
discuss them using sample scripts to demonstrate the features.
The UserTalk Scripting Language
UserTalk is a full-featured language, with looping, if-then-else, case
statements, local and persistent variables, subroutines, error recovery and automatic
type coercion. UserTalk’s syntax is most like C or Pascal. The language is very tightly
integrated with Frontier’s object database, discussed in the “Storage System” section,
below.
The goal of the language is to make it easy to write utilities that operate at the
system level; launching and communicating with applications, managing the file
system and operating system and other system resources, and moving information
around a network.
Hello World
There’s a long tradition of introducing languages with a simple “Hello World”
program. Here’s what Hello World looks like in UserTalk:
/* 1 */
msg ("Hello World!")
This displays the string in Frontier’s main window:
Built-in Verbs
Let’s look at a more comprehensive script that creates aliases of all applications
on all hard disks in the Apple Menu Items folder:
/* 2 */
local ( appleFolder = file.getSpecialFolder ("Apple Menu Items"))
fileloop (f in "", infinity) scan all disks, to infinite depth
if file.type (f) == 'APPL' it’s an application
local (name = file.fileFromPath (f))
if dialog.yesNo("Create alias of “" + name + "” in Apple
menu?")
file.newAlias (f, appleFolder + name + " alias")
The script loops over all files, and when it finds one whose type is 'APPL', it
displays a “yesNo” dialog asking if you want to create the alias. If you click on Yes, the
script calls the Frontier built-in file.newAlias verb to create the alias in the Apple
Menu Items folder.
“fileloop” is a special construct in UserTalk, it allows you to iterate over all the
files on a disk or in a folder. If the path is the empty string, fileloop will loop over all
mounted disks. By saying we want to go to infinite depth, the loop will visit all files in
all sub- folders, no matter how deeply nested.
We could have hard-coded a path to the Apple Menu Items folder, but by calling
file.getSpecialFolderPath this script will work on any Macintosh, in any country. For
example, in Fredonia version of System 7, this call will return
“Sturgeon:Smidgadzchen Festerest:Apfel Menu Gethingies”. (With apologies to the good
citizens of Freedonia)
file.getSpecialFolderPath, file.type, file.fileFromPath, dialog.yesNo and
file.newAlias are examples of calls to built-in verbs. Generally, if the Macintosh OS
provides an API for a system-oriented operation, Frontier provides a simple scripting
API for that operation.
As examples, Frontier 2.0 includes built-in verbs that allow you to:
• Launch an application, data file, control panel, or desk accessory. Loop over all
running applications. Bring an application to the front. Access the desktop
database.
• Move, copy, delete or rename files and folders. Get and modify file/ folder
attributes such as the creation date, modification date, file type and creator, size,
file comment, version information, icon position. Determine whether a file is
visible or not visible, locked or unlocked, busy or not. Reconcile the changes
between two versions of the same folder.
• Manage the resource fork of any file. Read and write text files and data files.
Access the system clock. Move data in and out of the clipboard.
Frontier is itself completely scriptable and includes built-in verbs to manage its
object database script, outline, text and picture windows.
Interapplication Messaging
Scripts that drive applications look very much like scripts that drive the file
system and operating system.
Here’s a script that creates a StuffIt archive containing compressed versions of
all files on all disks modified after May 15, 1993:
/* 3 */
local (archive = "System:Desktop Folder:Changed Files.sit")
if file.exists (archive)
file.delete (archive)
StuffIt.launch ()
StuffIt.newArchive (archive, false) create a 3.0-format archive
StuffIt.bringToFront ()
fileloop (f in "", infinity)
if file.modified (f) > date ("May 15, 1993")
StuffIt.stuffItem (f)
StuffIt.closeArchive (archive)
The new archive appears on the desktop. We delete the file if it already exists.
Then we launch StuffIt Lite 3.0, create the new archive, and loop over all files on all
disks. If a file’s modification date is greater than May 15, 1993, we add the file to the
archive. When the loop completes, we close the archive.
The verbs StuffIt.newArchive, StuffIt.stuffItem, and StuffIt.closeArchive result
in an Apple Event being sent to the StuffIt application. But this fact is invisible to the
script writer. You call StuffIt from a script exactly as if you were calling a built-in
verb. The only difference is that you have to launch the StuffIt application in order to
call it.
Here’s what the script for StuffIt.stuffItem looks like:
/* 4 */
on stuffItem (path)
return ( appleEvent (StuffIt.id, 'SIT!', 'Stuf', 'path', string
(path)) == 0)
More information on the built-in appleEvent verb is included in the Q&A section
at the end of the article.
Object Model Scripting
Some applications implement the richer “object model” style of Apple Events.
Scripts that drive object model applications can look very different from scripts that
drive simpler scripting APIs, as illustrated in the previous example.
Here’s a script that opens a FileMaker Pro 2.0 database containing information
about the 50 states in the United States. It creates a text file that lists each state and its
capital on a separate line:
/* 5 */
local (textfile = "System:States Text", database = "System:States
Database")
file.new (textfile) create the file, its length is 0 bytes
file.setType (textfile, 'TEXT') it's a text file
file.setCreator (textfile, 'ttxt') it can be opened by TeachText
file.open (textfile) open the data fork of the file
app.startWithDocument ("FileMaker", database)
with FileMaker, objectModel use FileMaker/object model vocabularies
local (i)
show (record [all]) select all records
for i = 1 to count (layout [1], record) loop over all the records
local (stateName, stateCapital)
stateName = get (record [i].cell ["Name"])
stateCapital = get (record [i].cell ["Capital"])
file.write (textfile, stateCapital + tab + stateName + cr)
file.close (textfile)
FileMaker.quit ()
In this example, we create a text file on the disk named System, set its type and
creator, and open its data fork. We launch the FileMaker application, telling it to open
the States Database.
We access FileMaker’s terminology by including the FileMaker-related script
code inside the “with” statement. When we refer to count (layout [1], record) we’re
asking FileMaker for the number of records when viewed thru the first layout. We
make sure all the records are selected. Then we loop over all the records in the
database, copying the state name and capital into locals, then writing a line to the text
file. After looping over all the records, we close the text file and quit the FileMaker
application.
Script editor
Frontier has a full-featured, integrated script editor.
Here’s what the first sample script looks like in a Frontier script editing
window:
Frontier scripts are outlines. You could collapse the fileloop statement to show
none of its sub-heads, or one of them, or all of them. You can control the level of detail
you want to view. Outlining also helps you edit the structure of your scripts. Drag a
headline to a new location, and all the sub-heads move with it. Programs are
hierarchies. Outliners are hierarchy editors. It makes a lot of sense to have a script
editing tool that understands hierarchic program structure.
One of the fallouts of this design is that structure symbols such as curly braces
and semi-colons are unnecessary when you edit a script using Frontier’s script editor.
The structure of the outline is the structure of the program and vice versa.
The window title shows the object database address for this script. It’s located in
the deskscripts sub-table of the system table. Its name is appsToAppleMenu. Details on
the object database are in the “Storage System” section, below.
This screen shot was taken using a development version of Frontier that supports
multiple scripting components. To the left of the horizontal scrollbar is a popup menu
that allows you to select the scripting component to run the script. This allows you to
edit an AppleScript script within Frontier and call it from any other script. In fact,
you can edit a script in any OSA-compatible scripting language. This includes a new
version of CE Software’s QuicKeys macro utility, currently in development.
You can find and replace within a single script and over groups of scripts. Options
include case insensitive searching, wrap around, find only language identifiers or
whole words. Here’s a screen shot of Frontier’s Find & Replace window:
The script editor is itself scriptable, so you can write tools that automate script
development.
Debugger
The script editor also provides the interface for debugging. Here’s what a script
editing window looks like after you click on the Debug button:
The buttons at the top of the window are handled by Frontier’s integrated script
debugger. You can step from statement to statement, go into a script call, step out from
a script call, follow statement execution, resume normal execution, or halt the script.
You can examine and edit all local and global variables in the storage system while any
number of scripts are running. You can set a breakpoint at any statement.
For example, if you set a breakpoint on the file.newAlias call, and clicked on
Lookup with the name appleFolder selected, as shown below:
You’d see the top-level stack frame for this script. This table is fully editable:
To resume running the script, bring the script window to the front and click on
Follow or Go.
When a syntax or runtime error occurs, you can jump to the line where the
error occurred, with all script editing features enabled. You can view all local and
global variables before terminating the script.
Storage System
Frontier’s built-in storage system is called the object database. It can store
small objects like a user’s name, or the time of the last backup; or larger objects like
a list of users, a standard form letter, or a table of electronic mail accounts. It makes
it easy for scripts to communicate with each other, e specially scripts running in
different threads. The object database is a permanent data structure, so you don’t have
to worry about complicated file formats for your scripts.
The object database starts with a top-level table is called “root.” Start by
clicking on the flag in the Frontier’s main window. It reveals four buttons:
Click on Object DB button. Frontier opens the “root” table:
There are eight items at the top level of the database. Some are still on disk.
Frontier only reads in tables as they are needed. If you double-click on the wedge next
to examples, the sub-table opens:
Values can be small things like booleans, characters and numbers; or large things
like strings, word processing text, outlines or scripts. Frontier supports over 20