Gotta vent... I HATE AppleScript!!!

Grrrr!!!

I find the supposedly English-like syntax of AppleScript maddening. It might make the scripts more readable, but, for me at least, I find it almost impossible to write AppleScript, to stumble upon the right combination of words that will constitute valid statements. I’m reduced to looking for sample code for practically every litte thing I want to do, cautiously making variations for what I want to do, half the time only finding new ways to create syntax errors.

I’ve looked at many online resources, including at Apple’s web site. Every bit of documentation I find is woefully incomplete. When looking at sample scripts I keep finding stuff that I never, ever saw in any AppleScript developer’s guide. It’s like half of the language’s keywords are secrets, meant only to be acquired by patiently collecting them from dozens of scattered sources.

I’m going to go hunting for a book on AppleScript today, hoping that someone somewhere has put together a comprehensive guide to this obnoxious language that won’t leave me merely experimentally typing stuff, hoping to stumble upon acceptable syntax and useful semantics by chance.

AppleScript: The Definitive Guide
by Matt Neuburg
published by O’Reilly
recommended by Apple Developer Connection
Covers Mac OS X Panther

After looking at the books available, I believe this one to be the best. It’s very thorough, and the author himself is all too-familiar (but honest) about AppleScript’s shortcomings. He even talks about the same problem you have with AppleScript’s “English-like” syntax.

–Joshua

If you’re having problems with the AppleScript language, you might consider using Python instead. While not quite as attractive to look at, it has a much more regular, well-defined and unambiguous syntax that you may find much easier to learn and write. There’s also plenty of documentation and tutorials available online and in print, along with the popular comp.lang.python newsgroup.

While Python currently lacks full OSA support [1] its new application scripting system, appscript, is maturing nicely and in most areas is already as good as - sometimes even better than - AppleScript. A binary installer will be available shortly, or you can install it manually if you prefer [2].

e.g. Here’s a simple script to list the name and email(s) of every person in Address Book with one or more email addresses:

from appscript import *

peopleRef = app('Address Book').people.filter(its.emails != [])
print zip(peopleRef.name.get(), peopleRef.emails.value.get())

You can also get interactive help on commands, classes and references by calling appscript’s powerful built-in help(). e.g. To list the properties and elements of a reference:

app('Address Book').people.help()

HTH

has

[1] A PythonOSA component is in development, but doesn’t yet support event handling so can’t currently be used for tasks like attachable Mail rules and Folder Actions.

[2] See the PythonMac SIG mailing list if you need any help setting up and using MacPython or Distutils.

Believe me, I’ve considered alternatives. The problem is that there is no standard installed OSA Python or anything else. There’s an OSA JavaScript that I’ve seen – which would be perfect if it were standard on all Macs and further developed than it currently is. I don’t want to need to ask users to install special packages on their Mac simply to use my software.

I’m also beginning to wonder if any OSA-compliant scripting language can interact with stdin/stdout/stderr beyond an initial input and a final output upon script termination.

Here’s the problem: I’m writing cross-platform Java, not Cocoa/Java. The Windows version of my app works with iTunes by executing a JavaScript via Runtime.exec(). It’s a bit of a hack, but it works reasonably well, and the irony is that JavaScript on Windows is more powerful, speedy, and convenient for controlling iTunes than AppleScript on a Mac is turning out to be.

In the Mac version of my app, the only way to control iTunes is via AppleScript or raw AppleEvents. I need AppleScript because I don’t want to have to (and don’t think I can) make my mostly-pure Java app interact by sending and receiving raw AppleEvents. That’s more complicated than the complicated mess I’m trying to solve, if it’s doable at all.

This code illustrates the dilemma I face:

public static void test()
{
  long    t = System.currentTimeMillis();
  String  result;
  int     n;

  result = doScript("set idList to ""n" +
                    "tell application "iTunes"n" +
                    "  set lib to library playlist 1n" +
                    "  set n to count of file tracks of libn" +
                    "  repeat with i from 1 to nn" +
                    "    set aTrack to file track i of libn" +
                    "    set idList to idList & (database ID of aTrack) & returnn" +
                    "  end repeatn" +
                    "end telln" +
                    "idList");
  System.out.println("--- " + ((System.currentTimeMillis() - t) / 1000.0) + " secs ---");

  t = System.currentTimeMillis();

  n = Util.to_int(doScript("tell application "iTunes"n" +
                           "  set n to count of file tracks of library playlist 1n" +
                           "end telln" +
                           "n"));
  System.out.println("n: " + n);
  result = "";
  for (int i = 1; i <= n; ++i) {
    if (i % 100 == 0) System.out.println("=" + i);
    result += doScript("tell application "iTunes"n" +
                       "  set anID to database ID of file track " + i + " of library playlist 1n" +
                       "end telln" +
                       "anID") + "n";
  }
  System.out.println("--- " + ((System.currentTimeMillis() - t) / 1000.0) + " secs ---");
  System.out.println(result);
}

This shows two ways of doing the same thing: creating a list of database IDs of songs in the iTunes Library. (The “doScript()” method handles all of the NSAppleScript stuff needed to execute a String as a script.)

The first approach creates a script that only needs to be compiled once, which does the whole task and doesn’t report back until completion. The second approach allows feedback for progress, and, in a more complete version of the code, would allow interaction with Java features like Hashtable objects, but the inner AppleScript statement has to be compiled each and every time through the loop, and it has to generate a lot more extra AppleEvent traffic because I can’t hold onto any AppleScript state information from one statement execution to the next.

I’d tell you how much the measured performance difference turned out to be, except for this:

Executable “MyApp” has exited due to signal 11 (SIGSEGV).

Somewhere around half-way through a nearly 4000-song library, all of this AppleEvent and script compiling activity simply crashes my app.

No bitwise operators either? Am I missing something, or am I again finding another gaping hole in the completeness of AppleScript?

I was getting desperate enough to try to write my own hash table implementation, figured I’d do something quick and dirty with some bit-shifting and XOR-ing… nope. Can’t even do that.

Sigh.

Like I say, you don’t need OSA-compliant language to use Apple events. You only need an OSA language component for stuff like attaching scripts to applications. There’s an Apple-installed Python.framework in OS 10.3+, and nothing preventing you from bundling dependencies with your app where you need to - Python’s licence is extremely liberal and appscript is LGPL’d.

OSA comes from an event-driven background and predates OS X, so it doesn’t integrate all that well with the unix environment or unix-based scripting languages. Right now, best thing to do is file a feature request with Apple asking them to update the OSA API to take advantage of both.

You know iTunes/Win also has a COM interface? You might find that a better option.

[quote]
In the Mac version of my app, the only way to control iTunes is via AppleScript or raw AppleEvents.[/qoute]

Or any other language that can talk Apple events: Perl, Python, Tcl, etc. Also, if you don’t mind bridging to the Carbon APIs for a few calls, another option is AEBuild.

First, understand that application object references are relational queries, not OO references. That means (application implementation willing) that a single command (in this case ‘get’) can operate on more than one object at a time, e.g.:

tell application "iTunes"
	set lib to library playlist 1
	set idList to database ID of file tracks of lib
end tell
set AppleScript's text item delimiters to return
return idList as string

Second, learn how to call handlers in a compiled script using NSAppleScript executeAppleEvent:error:. That’ll allow you to pass in arguments instead of using nasty code generation kludges.

HTH

has

Only that it’s a very small, simple language (by design) with a tiny, worthless standard library (by neglect) [1]. For a professional-level scripting language you should look elsewhere.

has

[1] i.e. The Standard Additions osax, and not much else. There’s a few third-party osaxen (e.g. http://osaxen.com/) and vanilla AS libraries (e.g. http://applemods.sourceforge.net/) available, but it’s still quite pathetic what’s available for AS compared to most any other language.

No, but I think you need an OSA-compliant language to use AppleScript’s simple object model of talking to other applications, rather than having create AppleEvents and handle tangles of asynchronous responses gathered through event hooks.

Indeed I do know iTunes has a COM interface. That’s in fact the way the JavaScript I’m using talks to iTunes – through the COM interface.

Please take into account that what I’m writing here is a cross platform Java application. If you haven’t worked in Java, you might not be aware of some of the limitations that entails. I believe Microsoft’s own, now abandoned, Java Virtual Machine had special non-standard Java features for doing COM, but it’s also a very old and primitive Java, mostly equivalent to Sun’s Java 1.1, and way too much of my code depends on features from Java 1.2-1.4.

On Windows, the easiest way for my Java app to get to the iTunes COM interface is indirectly through a JavaScript. I’m trying, perhaps in vain, to do a very similar thing in the Mac version, where my Java app’s access to iTunes will be through an AppleScript, indirectly using AppleEvents just as I’m indirectly using COM on Windows.

  1. I’m bitching and moaning about AppleScript not because no possible alternatives to the problems I’m trying to solve exist, but because, if AppleScript had just a few basic features that I think one should reasonably be able to expect in a modern computer language, I’d have solved the problems I’m trying to solve a long time ago.

If the only issue were possible alternatives, I could learn Objective C, rewrite my entire app in Objective C, and maintain two entirely different code bases for the Mac and Windows versions of my app. Oddly enough, I’d rather not do that. :shock: :?

  1. I’m writing a Java application that has to run on both Mac and Windows. Java apps like this have their relationship to AppleEvents mostly hidden from you when running on a Mac. The same code has to run on Windows, which obviously has no clue what an AppleEvent is. Yes, you can do some “if (I’m on a Mac) then…” kind of logic, but even then, I don’t know if I can hack into sending and receiving AppleEvents very well.

  2. I’m writing a GUI application for use by non-technical users. Any solution involving Perl or Python, etc, has to be something I can invoke through my Java GUI, and shouldn’t involve asking users to install this or that package to make the app run. I think there are some extension libraries for AppleScript that might be useful to me, but I’m completely uncertain how to use such things when launching AppleScript from a Java app.

  3. I’m writing this app as open-source freeware, so I can’t use any commercial code to solve my problems.

I have to confess, I’m not understanding the distinction you’re trying to make.

Does this have anything to do with “call method”? I posted a question about that here a week or so ago, and received only deafening silence in reply.

Can I do any of this stuff short of trashing my cross-platform Java app and starting over?

It’s almost beginning to sound like the solution is writing an independent app to handle the iTunes stuff I want to do, which my Java app launches and communicates with by whatever primitive means it can.

Doing these things via AppleEvents also means turning straight-forward things like “set aName to name of first track in library playlist 1” into dozens of lines of code that generate one or more AppleEvents and then stringing together asynchronously received AppleEvent responses before you finally have “aName”, correct?

If that’s what it takes, that’s what it takes… But if that’s what it takes, I’ve still got a major “I HATE AppleScript!!!” feeling going on here, because a few simple reasonable features in the language would be saving me a lot of grief.

That’s probably because you’ve logged onto a BBS system where the users for the most part really like the technology it is centered around. Ranting and raving about how horrible applescript is in comparison to what you normally program in, and declaring your disgust for it, probably isn’t an effective method of enticing others to help you. Especially when many of these people make a living employing applescript. I generally find the people out here to be extremely helpful, and always very knowledgable.

Look, I understand your frustrations, and have run into a few brick walls myself. But there is always a solution. You may not always like that solution, but maybe that is a sign that what you are trying to do is currently not possible the way you want to do it.

Probably a dumb question but have you checked out Developer Tools?

Or, have you asked/complained to anyone at Apple or on the Apple boards?

In the end you may even still have to write an independent app that handles the iTunes calls and handle the primitive means to the end separately. No matter what you end up doing - remember that you’ll get more help if you keep some of your disgust to yourself.

Best regards

I’m well aware of BBS/newsgroup etiquette. Your statement above presupposes that I can in here ranting and raving from the very start. That is not the case. I asked for help quite politely, thank you very much.

I’m guessing that I got no answer about the “call method” thing because the feature is probably not all that well documented, so everyone else out there, even with more AppleScript experience, is probably as much in the dark as I am about it.

And come on, I started the topic title with “Gotta vent”… not four-letter words and “applescript is teh sux0rs!!1!!1”, so I’d hope that most people would take my expressed frustration with a sense of humor.

Rather than feel slighted by my comments about AppleScript, I imagine that a lot of other people here must share my frustrations and probably feel better knowing that there are other people who feel the same way.

As for “there are always other solutions” – sure there are. But in this situation, it’s like AppleScript takes me 95 miles of a 100 mile trip, but for some obscure reason, the option of walking the last five miles is closed – only walking back 95 miles and getting another car will get me where I’m going.

Okay, I’ll go against my better judgement, and respond once more.

I didn’t imply anything of the sort, but now that you mention it…

I’ll be fair, you did end that last one with “I’d be most appreciative”.

I’m certainly not looking to get into a flame-war here. I’m just saying that the above could potentially be taken the wrong way, and make people less apt to respond. Just trying to help you get a better response, that’s all. Etiquette aside, you do yourself a disservice by assuming (in your posts) that everyone here is “in the dark” and by telling folks that you are imagining “whistling tumbleweeds” as a response. I just don’t see the point in including that in your your post - it is counterproductive.

That said, I wish I could help you. But, I am in the dark. Best I can do is the developer tools link but I’m guessing a smart feller like you has already exhausted that resource.

Sorry I couldn’t help more.

(Apologies for slow reply - been away a few days)

Nope, all you need for that is a connection to the Apple Event Manager API. e.g. Python has three layers: Carbon.AE, a very thin wrapper around AEM’s API; aem, which provides a efficient, mid-level API that uses raw AE codes; appscript, which provides terminology support and syntactic sugar for ease of use. About the only thing the OSA API gives you is attachability.

Apple management broke up the AppleScript dev team shortly after the v1.1 release and have mostly neglected it since. Recent developments like Studio are nice, but the core language has hardly evolved at all: thus it’s a fourteen year-old language with all the maturity of a one year-old. This should explain a lot.

Euww, shouldn’t use conditionals. You’ll never make ALL your Java code platform-independent; instead, separate all the platform-specific Java+Other code from the platform-independent Java code. Define a nice standardised API - countTracks(), namesOfTracks(), etc. - to separate the two, such that the platform-independent code doesn’t need to know anything specific about the code on the other side. The build process then includes either the Windows- or Mac-specific code depending on the target platform.

Include additional dependencies in your application bundle. That should make it nice and portable.

All the options I’m suggesting are OSS-compatible.

I have to confess, I’m not understanding the distinction you’re trying to make.

In conventional OO languages, you typically get a list of all objects and then iterate over that list, sending a message to each object in turn. (This is what your code was doing.) With Apple events, you construct a query that identifies all the objects you want to manipulate, then send a single command to the application with that reference as an argument. (This is what my equivalent example does.) The difference is cost: it is much, much cheaper to send one Apple event that acts on a hundred application objects than send a hundred Apple events that operate on one object each.

Aggregate operations are not only powerful and cool, with Apple events they are also often essential if you don’t want to drag your system to a crawl.:wink:

‘call method’ is a Studio command for sending messages from AppleScript to Obj-C. NSAppleScript is a Cocoa wrapper around part of the Carbon OSA API used to implement attachability in Cocoa-based apps. This will allow you to attach a compiled AppleScript script to your Java application, allowing your Java application to send messages to the script. For example:

http://www.macdevcenter.com/pub/a/mac/2003/02/25/apple_scripting.html

Of course.

It’s a chore doing Apple events from the system APIs, yes. AEBuild is probably the least painful option, but still quite tedious. That’s why it makes sense to use a higher-level API. If you want to use AppleScript as your high-level bridge then use NSAppleScript, not code generation, and parameterize your input data. Or use Perl/Python/Tcl/etc. and include all dependencies in your application bundle to ensure portability. In either case, use aggregate operations, not iteration, whenever possible to reduce the amount of [slow] Apple events being sent.

OR, if you’re feeling especially enthusiastic, you could write a high(er)-level Java API to wrap the Carbon AEM API. (I’d recommend using Python’s Carbon.AE and aem modules as a guide - one of my goals in writing aem is providing a roadmap for other languages to follow and will be happy to advise folks in porting it.) That might take you a bit longer to do, but it’d give you a native API that’s fairly easy to use and by open-sourcing it you’d be doing other Java developers a real service to boot.

HTH

has

I tried one aggregate operation, “location of every track in lib”, as opposed to iterating through each track in lib (defined as library playlist 1, btw) and asking for the location of each. In the Script Editor, it made an enormous difference – but then again, Script Editor seems to add a lot of debugging overhead that slows things down

In an text file executed via osascript… not so much. 0:59 vs 1:02 for around 3700 iTunes files.

Perhaps asking for the location is a special case, since iTunes has to check the file system to see if the last known location of each track is still valid – this is a way to hunt for dead tracks, after all. Something like “name of every track in lib” goes much, much faster.

I was about to ask you another question, but I simply experimented and discovered this:

{name, artist} of every track in lib

…now that looks like it will be a real time-saver – although if I throw in “location” that bogs the whole thing down again – in fact, at the moment, it’s beginning to look like the query with location in it will never come back. Is there a maximum payload size for the data returned?

By the way… if it is possible to ask for too much data at once, and I need to break things up, then suppose I do this:

set theNames to name of every track in lib
set theArtists to artist of every track in lib

Presuming that no one else is playing with iTunes between the execution of one statement an the next, can I safely assume that both lists have information is matching order, that song name 1 matches with artist name 1, etc.?

Another question… this doesn’t work:

name of tracks 1 through n in library playlist 1

Although these things do:

name of every track in library playlist 1
name of track 1 in library playlist 1

Is there a correct way to specify a range that I’m missing?

Could be. You’re still at the mercy of the application’s internal implementation; if that’s dog-slow it’ll easily outweight any IPC overhead you might have.

That’s two ‘get’ events: one to get every name, one to get every artist. AS just lets you write it that way as a convenience.

Not AFAIK. (If there is, Apple doesn’t say.) One thing I do know that sucks is building really large lists - the algorithm for packing items into an AE list is quite inefficient and I suspect there’s a lot of memory reallocation going on as new values are added.

Building an AE list of a few thousand file aliases shouldn’t be a bottleneck though; as you say, the major performance hit is probably occurring elsewhere, e.g. chatting to the filesystem.

has

iTunes’ scripting support isn’t the best. Check its dictionary to see which reference forms are supported for track elements:

Class playlist : (inherits from item) a list of songs/streams
Plural form:
	playlists
Properties:
	duration integer [r/o] -- the total length of all songs (in seconds)
	index integer [r/o] -- the index of the playlist in internal application order
	name Unicode text -- the name of the playlist
	[...]
Elements:
	track by numeric index, name, id

You can get tracks by index, name or id, but not by relative position, range or test. (Yes, this is lame; feel free to submit a feature request with Apple on improving it.) You’ll either need to get the names of all tracks and slice the resulting list in AS (quickest if you need many values), or get the name of each track one at a time (quickest if you need only a few).

has

Yep, elements are ordered so you should be safe as long as no changes are made within the time it takes to process both events (which is - hopefully - short).

has

Hi,

No. Range reference forms return lists in a statement while the every element reference form is a special reference form where you can get properties of the elements. However, you can use the filter in combo although it may not be efficient. Something like this:

tell application “iTunes”
set pl to library playlist 1
get name of every track of pl whose index > 5 and index < 11
end tell

gl,

Yep, believe it or not, this is faster:

–set t1 to the ticks
tell application “iTunes”
set pl to library playlist 1
set name_list to {}
repeat with i from 6 to 10
set end of name_list to (name of track i of pl)
end repeat
end tell
–set t2 to the ticks
–set t to t2 - t1
–display dialog t
return name_list

than this:

–set t1 to the ticks
tell application “iTunes”
set pl to library playlist 1
set name_list to (name of every track of pl whose index > 5 and index < 11)
end tell
–set t2 to the ticks
–set t to t2 - t1
–display dialog t
return name_list

gl,