Archive for the ‘AppleScript’ category

An AppleScript Quine

November 3, 2007

A few days ago, out of morbid curiosity, I went looking for a a quine written in AppleScript. To my surprise, I couldn’t find one.

I’d never actually written a quine before, so this was a good ‘excuse’ to make one (and brush up on some computability theory — use it or lose it).

Well, here it is. When run it will make Script Editor create a new window containing the source code.

This might seem like a strange way of printing. But AppleScript doesn’t have a printf function per-se. I thought about using display dialog to show the source, but the text wouldn’t be copyable, making bootstrapping difficult. Letting the source-string be the result of evaluating the script wasn’t a good solution ether, because Script Editor puts an extra “” around any result that’s a string. Using osascript to run the script could work, but I think it would be breaking the “spirit” of AppleScript to force the quine to be run from the command-line.

Unfortunately, I encountered what appear to be Leopard bugs in Script Editor. (Update 2008-10-11: this problem seems to be fixed.)
When bootstrapping/testing, the result window would sometimes be filled with…something inexplainable.For example

«handler ASCII character of item i of x»
quote pi 40 of string_from_ASCII_numbers(set_d_to)
100
quine
((5 blank lines))

I don’t know why, I don’t know how. But it’s a troubling inditement of AppleScript support in 10.5.0.
I could reproduce a problem like this:
* quit Script Editor
* open the quine in Script Editor, and run it.
* run the resulting window (“Untitled”)
* run the resulting window (“Untitled 2”)

* around “Untitled” 5 or 6, the result was “100”.
* keep iterating like this, and see what unpredictability awaits (the most iterations I could get before Script Editor crashed was 12).
In Tiger, the problem couldn’t be reproduced AFAIK.

I hope this regression is an isolated incident. It’s just one more reason I’m not fond of AppleScript.

Better AppleScript Through Expressions

October 13, 2007

When writing a short AppleScript to complete a specific task, do not write a procedure that checks if A exists, then operates on it if it does. Instead, write an expression that operates on A, and ignore any exceptions caused by A not existing. Surrounding the statement in a try-block will ignore any exceptions it generates. The where and whose commands can help. This almost always leads to cleaner, and faster, AppleScripts.

Here is an example, a re-write of code in EyeTV version 2.5 (if you have it, you can find this script at EyeTV.app/Contents/Resources/English.lproj/EyeTV\ Help/openreadme.scpt).

When run, this opens a readme inside the EyeTV application bundle, iff the EyeTV application is running. I did not write this, and I do not know exactly how it is used, or what constraints it was written under — just what it does.

on run
set readmePath to "Contents:Resources:English.lproj:EyeTV Help:eyetvreadme.rtfd"

tell application "Finder"
 set allApps to every application process
 
 repeat with i from 1 to number of items in allApps
  set thisApp to item i of allApps
  
  if creator type of thisApp is "EyTV" then
   open file (((application file of thisApp) as string) & readmePath)
   exit repeat
  end if
 end repeat
end tell
end run

Re-writing the code by making it an expression that assumes the correct application process exists has a dramatic difference.

on run
try
 tell application "Finder"
  set theEyeTVProcessBundle to file of first item of (every application process where creator type is "EyTV") as string
  open (theEyeTVProcessBundle & "Contents:Resources:English.lproj:EyeTV Help:eyetvreadme.rtfd") as alias
 end tell
end try
end run

We no longer need if or repeat blocks — they are procedural constructs that were used to find the correct application process, and guard against trying to open a file that does not exist. (I also removed the variable readmePath because I think the a path to “eyetvreadme.rtfd” is clearly a readme-path).

But it gets better. We could try removing the run and try blocks as well, and just ignore any errors the interpreter complains about. That brings the the script down to four lines. We can make it one-line: tell application "Finder" to open ((file of first item of (every application process where creator type is "EyTV") as string) & "Contents:Resources:English.lproj:EyeTV Help:eyetvreadme.rtfd") as alias, but I think that makes the script too hard to understand, because the name “theEyeTVProcessBundle” documents the long expression needed to reliably find the application bundle of the running process.

One line or four lines, that’s still a big reduction from 13 lines. Now it’s short enough to embed directly in Cocoa code with NSAppleScript.

Here’s another example. This is from IMLocation‘s uninstaller code, it It removes the IMLocationHelper from the list of items that will be opened at login. Here’s the original code:

tell application "System Events"
 if (login item "IMLocationHelper") exists then
  delete login item "IMLocationHelper"
 end if
end tell

Applying the approach of re-writing the procedure as an expression

try
 tell application "System Events" to delete (login item "IMLocationHelper")
end try

Taking things a step further, by removing the try-block, and just ignoring errors when running the one-line script: tell application "System Events" to delete (get login item "IMLocationHelper"). This makes a big difference in the clarity of the surrounding Cocoa code.

AppleScript is a pretty poor language on it’s own. Usually I see it being used to get some specific thing done. In other words, generally AppleScripts are essentially expressions, used to get or set a single value; rarely are they sub-programs. This is whyExpressions are such a good fit.

A big thanks to Uli Kusterer for correcting my doubleplusungood code! (details in comments)

(NS)AppleScript Sucks

September 24, 2007

EDITED TO ADD 2007-11-02: Some of the issues discussed in this article, like Script Editor messing with your code, have been addressed in Leopard. But until the majority of OS X users are on Leopard, they’re still a problem for most people. Also, I think it’s worth documenting just how many issues AppleScript tools and support have had. So I am not planning on removing “fixed” problems, at least for a while. This article is about why I don’t like using AppleScript. Historical issues are part of that dislike. It’s a matter of trust and expectations.

The NSAppleScript honeymoon is over. I don’t like AppleScript, I don’t like NSAppleScript.

John Grubber has a good article on AppleScript’s “natural language” failings. An excerpt (emphasis mine):

INTERPOLATION ON THE FAILED EXPERIMENT THAT IS APPLESCRIPT’S ENGLISH-LIKE SYNTAX

… It was a grand and noble idea to create an English-like programming language, one that would seem approachable and unintimidating to the common user. But in this regard, AppleScript has proven to be a miserable and utter failure.

In English, these two statements ought to be considered synonymous:

path of fonts folder of user domain
path to fonts folder from user domain
But in AppleScript, they are not, and rather are brittlely dependent on the current context…

The idea was, and I suppose still is, that AppleScript’s English-like facade frees you from worrying about computer-science-y jargon like classes and objects and properties and commands, and allows you to just say what you mean and have it just work.

But saying what you mean, in English, almost never “just works” and compiles successfully as AppleScript, and so to be productive you still have to understand all of the ways that AppleScript actually works. But this is difficult, because the language syntax is optimized for English-likeness, rather than being optimized for making it clear just what the fuck is actually going on.

This is why Python and JavaScript, two other scripting language of roughly the same vintage as AppleScript, are not only better languages than AppleScript, but are easier than AppleScript, even though neither is very English-like at all.

Amen, brother!

Many months ago I had written “In general, AppleScript code is extremely readable, and relatively close to english.” I do not believe this anymore, and have since revised the article. Too often AppleScript clashes with English syntax. Here are a couple of example AppleScript strings (both from current code v0.26):
@"tell application \"Adium\" to my status type as Unicode text";
@"set volume without output muted"

The first sentence is missing a verb (are we getting or setting?); the second lacks an object (set volume to what?). (It turns out the first gets a string from Adium, describing my status {“offline”, “away”, “available”, etc.}; the second un-mutes sound for the whole system.)

I don’t like the NSAppleScript class because it is extra un-thread-safe. That is to say, you can only use it on from the main thread (hint: performSelectorOnMainThread:). No amount of @synchronized blocks will keep you from crashing (intermittently!) if you use an NSAppleScript anywhere else. There is a workaround that won’t block anything, but the cure is worse then the disease. You can use an NSTask to have the osascript program interpret the script for you. This won’t block anything, but it will be take a long time, and is resource intensive.

Dynamically constructing an NSAppleScript is dangerous. There is no way to pass an array of arguments directly into the script, as can be done with an NSTask. Any quotation marks “” need to be carefully escaped. For example, the AppleScript built from [NSString stringWithFormat:@"tell application \"iChat\" to set status message to \"%@\"", message]; will fail spectacularly if message contains quotation marks.

AppleScript is also leaky. This is another reason why you should follow Apple’s advice, and always cache compiled NSAppleScript objects. Not only is it a time-penalty to recompile them; but you will also leak memory every time a new NSAppleScript object is created.

The Script Editor application sucks. Compared to any modern IDE it’s a bad joke. It’s not just that it’s underpowered — it’s that it’s obviously lacking polish. Error messages are often ungrammatical. A dead giveaway of poor design. (Although I get a perverse laugh out of “Syntax Error \ User canceled.”)

Script Editor won’t let you save or open code that won’t compile! This is a dangerous and serious bug. A user should always be able to save their data at any time. It turns out that Script Editor actually saves source code as a compiled script! So if your code can’t compile right now you can’t save it! I’ve essentially lost a script I wrote for the obsolete Fire IM client, because I can’t even read it without the program being present on my system, since AppleScript can’t “compile” it without finding the program.

Which hilights another problem with Script Editor — it likes to change your code; even if it means breaking it on other computers. If I type POSIX file "/Users/", it is automatically replaced by file "Macintosh HD:Users" when the script is compiled and saved. This is wrong. POSIX file "/Users/" is portable across any MacOSX computer that has the /Users/ directory — which is every desktop computer. But file "Macintosh HD:Users" relies on the computer having it’s root-directory reside on a disk named “Macintosh HD” — and this isn’t always true. The two statements are equavalent on my computer, but not on every computer — Script Editor is in error for forcing the change.

AppleScript comment syntax is broken:
(* this is a block comment in AppleScript*)
— and this is an inline comment
(* this comment should be valid — but it’s not because of the dash, and it will keep your script from running *)

In Objective-C mixing inline and block comments is permitted, /* this comment ... //won't break anything */ Even if no programming language allowed inline comment headers inside a block comment, AppleScript would still suck for doing this. That’s because unlike “//”, “–” is an actual punctuation mark. You can expect to find it inside a comment. (NOTE: “–” should be two single-dash characters, not the long-dash it was turned into. Unfortunately my blogging software always “upgrades” ASCII equivalents of punctuation marks.)

Paths in AppleScript are incompatible with everything else, and hard to work with. AppleScript uses pre-MacOSX style “:”-delimited paths. But every other popular programming language I know of does not not, and nether do any command-line utilities, or any shell I’m aware of. That makes using a command-line utility — which should be a simple task in any scripting language — a chore in AppleScript. For example, let’s say I was using perl, and I had a variable $src that held the path to some file. I could write:
`ditto -ckX --rsrc $src $src.zip`
to create a zip-file of it that preserved all its special MacOSX filesystem settings. $src could have been defined as “~/file”, or it could have come from another command-line utility, etc.

But in AppleScript I would have to write:
do shell script "ditto -ckX --rsrc " & POSIX path of src & " " & POSIX path of src & ".zip"
All the extra ‘<POSIX path of‘ nonsense makes for some unwieldy code. Worse yet AppleScripts’s support of POSIX path’s is deeply flawed. It won’t support ~ expansion. POSIX path of "~/file" resolves to file ":~:file", not file "Macintosh HD:Users:user:file" as it should. So all POSIX paths pased to an AppleScript have to be absolute.

What’s particularly funny about this, is that dragging a file into Script Editor inserts the poorly supported POSIX path to the file into the editor. This is another example of how poorly-designed Apple’s AppleScript IDE is.

AppleScript has no built-in regular expression support. As far as I know it’s the only modern scripting language without regex support. To use a regex in AppleScript, you have to invoke a command-line utility to do it for you, using the do shell script "" command. This introduces a level of “-escaping hell. It’s also very slow. Regex (un)support alone is plenty of reason to use anything but AppleScript for writing scripts.

The concept of libraries in AppleScript is deeply broken at the design level. Because AppleScript is such a minimal language, almost everything is done by asking other programs to do something. in AppleScript, functionality does not come from nested Libraries, but from “Scripting Additions” provided by other applications. This means there isn’t a logical and powerful library hierarchy that makes things easier. Instead you get an irrational flat list of modules, including every application on the system that is scriptable:
AppleScript Library Madness
It’s not always clear who you should “tell” to give you what you need. tell application "Finder" to get every application process gives a list of every running process. But why are we asking the Finder to give us that? The Finder is a file browser, and the “face” of the computer. If I wanted to see this list, I would launch Activity Monitor, or “top”, not mess around in the Finder. Functionality is not logically organized in AppleScript, and this makes writing code hard, because it’s difficult to find what you need.

AppleScript uses an application’s name to identify an application; unfortunately application names are a complex mess on MacOSX.

…there are at least five application names floating around, at least in concept: (1) the file name the Finder sees, which in the case of an application package is the package (bundle) name; (2) the name of the executable inside the package, (3) the long name used in many places for display purposes only; (4) the short name used as the application menu title and in a few other places where a long name won’t fit for display purposes; and (5) the process name of a running application. They aren’t always the same…

–From a message by Bill Cheeseman.

I still like the idea of directly embeding snippets of a scripting language into directly into Cocoa code. That’s a very powerful idea. But I do not like the leaky, and decidedly un-threadsafe implementation of NSAppleScript. I also dislike the AppleScript language and the AppleScript “IDE”. If AppleScript wasn’t currently the best way to script most Mac applications I wouldn’t use it.

Further Reading:
AppleScript likes to mess with your code
Computer Languages aren’t Human Languages

tell application “YourGreatApp” NSAppleScript is here to save the day

April 21, 2007

Cocoa includes the wonderful NSAppleScript class, which lets you embed a high-level AppleScript code snippets in your Objective-C program. AppleScript is the bee’s knees for interfacing with other applications, particularly Apple programs and OS X itself. EDITED TO ADD: Unless you use threads in your program. Here is a more sobering perspective, written several months after this article.

For example, say you want to get the user’s current status message from iChat. You can mess around with the InstantMessaging.framework — only to discover that it can’t do this — or you can wrap the following AppleScript:

tell application “iChat” to get status message

in an NSAppleScript object, like so:
NSAppleScript *iChatGetStatusScript; //this would be in the header.
/*Apple recommends only compiling NSAppleScript objects once, for performance reasons. Be sure to call [iChatGetStatusScript release] in your dealloc method.*/
if(!iChatGetStatusScript)
  iChatGetStatusScript = [[NSAppleScript alloc] initWithSource:
    @”tell application \”iChat\” to get status message”
];
NSString *statusString =
[[iChatGetStatusScript executeAndReturnError:&errorDict] stringValue];

Skeptical readers have probably noticed that I cheated by picking functionality that wasn’t in a framework for the last example. Unfortunately this situation is more of a rule then an exception. AppleEvents have been on the Apple software development scene longer then Cocoa. Lots of programs only expose functionality through AppleScript/AppleEvents. Using NSAppleScript is the best way to interact with these programs from Cocoa.

AppleScript is a big part of how FrontRow integrates with iTunes, even though they are both modern programs.
grok the ouptut of:
strings /System/Library/CoreServices/Front\ Row.app/Contents/MacOS/Front\ Row
(hint: look for “tell”), and you’ll see a lot of gems like:
tell application “iTunes” to get (data of artwork 1 of current track) as picture
Or:
tell application “Finder”
    set myCD to first disk whose format is audio format

Now I am the first to admit that a well-written Objective-C library trumps duck-taping one language on-top of another. But we don’t have the luxury of always being given well-written Objective-C interfaces.

As of version 0.24 IMLocation uses AppleScript to: interface with iChat and Adium; install/uninstall itself; mute/unmute sound; perform inter-process communication between the helper and GUI; and to get the MAC address of the first router connected to the ethernet port. NSAppleScript has also been heavily used during development for prototyping.

EDITED TO ADD: NSAppleScript is pathologically un-thread-safe. That is to say, you can only use it on from the main thread. No amount of @synchronized blocks will keep you from crashing if you use NSAppleScript in any other way. There is a workaround, but the cure is worse then the disease. You can use an NSTask to have the osascript program interpret the script for you. This won’t block your thread, but it will be take a long time, and is resource un-friendly.

NSTasks are the way to call the command line, or embed snippets of a different language in cocoa. I was originally using the do shell command AppleScript construct. If you are using it, please switch to NSTasks. They are much more powerful, still easy to use, and prevent all sorts of bugs caused by quote-marks in AppleScript.