The MacPerl Package

By: Vicki Brown

Because MacPerl runs on Mac OS, it has some different requirements than do Perl versions for other platforms. Some of the differences have to do with the filesystem, others with processes, inter-application communication, or built-in OS scripting. Because of these differences, special functionality has been included in MacPerl that is not found on other platforms.

Matthias Neeracher, the author of MacPerl, has written many MacPerl-specific modules and functions for the MacPerl distribution. These functions vary in complexity and utility, but they all have one main purpose: to make your Mac OS scripting easier and more powerful.

Note: The first time we introduce a function, we will list its expected arguments using single, fully capitalized words, with square brackets enclosing optional arguments, as:

FooBar(ABC [, DEF])
where function FooBar() takes the required parameter ABC and the optional parameter DEF .

MacPerl's built-in functions and variables are accessible just like any Perl function or special variable, with one caveat: they must be accessed via the MacPerl package. [1] [2] So, you must use the package MacPerl directive before referencing these items or access them (from outside that package) with the double-colon syntax. [3]

Volumes, Paths, And File Specifications

Probably the most noticeable difference between MacPerl and other forms of Perl is in the specification of pathnames. Mac OS syntax uses colons, rather than (back-)slashes, to separate the elements of the name. Also, there is no overall "root" for the file tree as a whole. [4]

Complicating the situation still further, Mac OS programs are able to use an alternate form of specifying a file: the file system specification record (filespec). A filespec is a special encoding of the path name which is guaranteed to be unique for every file. These lines could specify the same file:

$path = 'HD:MacPerl ƒ:Shuck Manual'; $fss = 'FFFF000114B7:Shuck Manual';

As used by MacPerl, a filespec starts with a character (possibly invisible, depending on your font) whose high (i.e., sign) bit is set. It continues with a series of letters and numbers followed by a colon and a filename.

Note: The "series of letters and numbers" encodes a unique identifier for the hard disk partition and folder that contain the file. [5] Unlike a path name, the identifier will continue to "point to" the folder as long as the folder exists (even if the folder is moved a different location).

If the file is removed and replaced by a file with the same name, the identifier will also continue to work. If the folder is removed, however, the identifier will not "find" a new folder with the same name. In short, the semantics of filespecs are not the same as that of path names.

In many situations, either a path name or a filespec can be used; open() , for instance, will accept either one. In some cases, however, a function may require a filespec instead of a path. Also, some functions return filespecs; if your program isn't ready for a filespec, it won't know what to do with it.

Luckily, the MacPerl package provides these translation functions:

MakeFSSpec(PATH) # convert path to filespec MakePath(FSSPEC) # convert filespec to path
Volumes() is a good way to practice with filespecs and paths. In a scalar context, this function returns the filespec of the startup volume. In a list context, it returns the filespecs of all available volumes, in the order that they were mounted (the startup volume is always first). The script below uses Volumes() to display these filespecs, sorted by volume name:

$svol = MacPerl::Volumes(); print "The path name of the startup volume is:\n"; printf(" %s\n", MacPerl::MakePath($svol)); print "The filespecs of the other volumes are:\n"; @vols = MacPerl::Volumes(); foreach $vol (sort(@vols)) { printf(" %s\n", MacPerl::MakeFSSpec($vol)) if ($vol ne $svol); }

Note that we called MakeFSSpec() on the volumes, even though they already are filespecs. This works because MakeFSSpec() and MakePath() return the right thing whether they receive a filespec or a path. If you aren't sure which one you have in your variable, feel free to pass it through either function to ensure that you will get what you need.

File Information

Each Mac OS files has a creator and a type. A document's creator (application ID) is the same as that of its assigned application. The type encodes the type of document (e.g., text or graphic file).

For instance, script files that "belong" to MacPerl have the creator McPL and the type TEXT . [6] Files created by MacPerl's open() function have, by default, the creator 'MPS ' (MPW's application ID) and type TEXT . [7]

SetFileInfo(CREATOR, TYPE, FILES)

applies the specified creator and type to a list of files.
GetFileInfo(FILE)
returns either the file type (in a scalar context) or an array ontaining the creator and type (in a list context).

This short script opens a directory and looks through all of the files. If a file is of type TEXT , the script changes the file's creator to R*ch (BBEdit's application ID).

#!perl -w my($dir, $file, @files); $dir = 'HD:Desktop Folder:Text Files'; chdir($dir); opendir(D, $dir) or die($!); foreach $file (readdir(D)) { if (-f $file && MacPerl::GetFileInfo($file) eq 'TEXT') { push(@files, $file); } } MacPerl::SetFileInfo('R*ch', 'TEXT', @files);

Answer, Ask, Pick

These three basic dialog functions are extremely useful for putting a quick Graphical User Interface (GUI) on top of an otherwise text-based script. They are normally the best way to get simple user input. [8]

Answer(PROMPT [, BUTTON1, BUTTON2, BUTTON3])

puts up a dialog box with a message ( PROMPT ) and a choice of buttons for the user to click.

If BUTTON1 is unspecified, it will be given the label OK . Thus, the calls

Answer('Wake Up')
and
Answer('Wake Up', 'OK')
do exactly the same thing. Both return a dialog box that says Wake Up and has a single, highlighted button that reads OK .

The buttons are displayed in reverse order. The first button passed to the function — which is the last button displayed on the screen — is always highlighted. So, the following call will print a dialog box with three buttons — Yes , No , and Maybe , in that order — with Maybe highlighted:

$ans = MacPerl::Answer('Are you awake?', 'Maybe', 'No', 'Yes');

Then, of course, you want to get a value back to find out what the user has clicked. The first button on the screen always returns a 0 . Since the last button passed to the variable is always first in the dialog box, clicking on OK in the following examples always returns 0 :

$an1 = MacPerl::Answer('Delete?', 'Huh?', 'No', 'OK'); $an2 = MacPerl::Answer('Delete?', 'No', 'OK'); $an3 = MacPerl::Answer('Delete?', 'OK'); $an4 = MacPerl::Answer('Delete?');

Ask(PROMPT [, DEFAULT]) is similar, but instead of having buttons to return a value, it has a text input box. Like Answer() , the first argument is the prompt. The second argument, which is optional, allows you to put some default text into the box for the user.

The value returned by the function is the text that is in the box when the user clicks OK . If the user clicks Cancel , the function returns undef .

Note: User-supplied data should always be considered tainted, and is usually tainted under Unix Perl. However, the special MacPerl routines generally do not taint this data. You might want to consider using the Taint module from the CPAN, with which you can intentionally taint any data you have. See Chapter 16, CGI Scripting, and perlsec for more details on tainting.

$name = MacPerl::Ask('What is your name?'); $quest = MacPerl::Ask('What is your Quest?', 'I seek the Holy Grail');

Pick(PROMPT, VALUES)

allows you to put up a scrolling list of items. Again, the first argument is the text prompt for the dialog box. But here, the number of following arguments is limited only by memory, and each argument will be a value in the list. Again, the selected value will be returned if the user clicks OK , and undef will be returned on Cancel .

$suit = MacPerl::Pick('Pick a Suit', qw(Hearts Spades Diamonds Clubs)); $card = MacPerl::Pick('Pick a Card', ('A', 2..10, 'J', 'Q', 'K'));

You can also have MacPerl::Pick allow the picking of multiple items by assigning it in a list context, as in:

@major = MacPerl::Pick('Your college major(s)', qw(Psychology Sociology Journalism));

Quit

Quit(LEVEL)
sets a flag that tells MacPerl whether or not to quit after the current script has finished execution. It does not quit until the script is finished, if at all. LEVEL defines under what conditions this happens, as:

0 Do not quit.

1 Only quit if this is a "runtime" script.

2 Always quit.

3 Only quit if this is the first script run since MacPerl was launched.

MacPerl::Quit(2); MacPerl::Quit(0) if MacPerl::Answer( 'Should I stay or should I go?', 'Stay', 'Go' );

$Version

$Version is the one item in the MacPerl package that is not a function. It is a read-only scalar variable that holds the MacPerl version information. This differs from the special Perl variable $] , which holds the version of the Perl sources from which MacPerl is built.

$Version always changes when a new MacPerl is released, but often those releases are based on the same Perl sources, so $] doesn't always change.

print "MacPerl $MacPerl::Version, Perl version $]";

Displays a line of the form:

MacPerl 5.1.9r4 Application, Perl version 5.004

LoadExternals

One really nifty feature of MacPerl is its ability to use other Mac OS languages.

LoadExternals()
is a way to use XFCN and XCMD extensions, which are commonly used in HyperCard scripting. The argument passed is a library file, with the same path rules as require .

Dartmouth College has developed some useful XFCNs and XCMDs. One of them, for Clipboard access, is on our CD-ROM. Copy Clipboard.XFCN to your site_perl folder. This file contains an XFCN that can copy information to and from the Mac OS clipboard, by means of the Clipboard() function.

If an argument is passed, LoadExternals puts it on the Mac OS clipboard. Otherwise, it returns the contents of the Clipboard. Note that, because LoadExternals() brings functions directly into the current package, many people prefer to call the function from a separate package.

package Dartmouth; MacPerl::LoadExternals("Clipboard.XFCN"); package main; Dartmouth::Clipboard("D'oh!"); print Dartmouth::Clipboard(), "\n";

If everything has worked properly, the specified text should land on the Clipboard ( D'oh! ). You can go to the Finder and select Show Clipboard from the Edit menu to prove it to yourself, if you like, or just paste it somewhere.

DoAppleScript

DoAppleScript(SCRIPT)
takes the text of an AppleScript as its argument.

$script = <<EOS; tell application "MacPerl" make new window copy "MacPerl: Power and Ease" to character 1 of front window end tell EOS MacPerl::DoAppleScript($script);

Reply

Reply(REPLY) is used primarily for returning data to programs that call MacPerl. For example, AppleScript can call MacPerl as follows: [9]

tell application "MacPerl" Do Script "MacPerl::Reply('Hello')" end tell

If run from Script Editor, Apple's AppleScript editor, this will return the text "Hello".

Where to go from here...

As you can tell, MacPerl is much more than "just Perl ported to the Macintosh". The MacPerl package (and other modules) provide you with the opportunity to do something previously available only to programmers who purchased expensive C development environments - program the Macintosh interface! We'll cover more Mac programming in future columns.

To learn more, read the online documentation under the Help menu in MacPerl. Visit the MacPerl web pages [10] to download examples. Join one of the MacPerl mailing lists [11].

If you've written some MacPerl code that takes advantage of the MacPerl package, or any of the other Macintosh-specific modules, we'd love to hear from you. Please consider contributing your code examples or knowledge in a future column!


Footnotes and references

[1] Actually, this isn't strictly true. They can also be exported into the current package by the MacPerl module if asked for explicitly:

use MacPerl qw(Ask Answer Pick); $x = Answer('Hello');

[2] MacPerl also includes many modules which are useful for dealing with the Mac OS Toolbox. There are quite a few of these, in packages that begin with Mac::, for example, Mac::Files, Mac::Processes, and Mac::Memory. Mac:Speech was the subject of Speaking of MacPerl in Issue 6 (November 1999).

[3] Interesting note: a package's symbol table is a hash. To see every symbol available in a given package, try:

foreach (keys(%MacPerl::)) {print "$_ => $MacPerl::{$_}\n"}

[4] See MacPerl Oddities: if ($^O eq 'MacOS') in Issue 2 (June 1999) and Perl: It's not just for Unix anymore in Issue 3 (July, 1999) for details.

[5] Specifically, this series of "letters and numbers" is a 48-bit hexadecimal number: a 16 bit number (vRefNum) followed by a 32 bit number (dirID).

[6] MacPerl itself has type APPL (as all applications do) and, of course, creator McPL. For more about creator IDs, see Chapter 13, The Toolbox Modules.

[7] If you know how to use ResEdit, you can change the default in MacPerl's GU I resource ID 12040. Do not attempt this if you do not know what you are doing.

[8] There are also several GUI modules for more advanced interaction; these are discussed in Chapter 14, GUI Toolbox Modules.

[9] Remember, this is AppleScript code and will not run as-is in MacPerl.

[10] See the Contributed Code Examples area for ideas and inspiration.

[11] Subscribe online.


The material above was previously published as chapter 12, The MacPerl Package in MacPerl: Power and Ease by Vicki Brown and Chris Nandor, publ. 1998, Prime Time Freeware. Reproduced with permission of Prime Time Freeware.