JavaScript vs. AppleScript

I’m years into my attempt to use JavaScript for Automation (JXA) more than AppleScript, but Apple and its crummy JXA documentation keeps getting in my way.

As an example of a case where I did use JXA, let’s look at a Keyboard Maestro macro I use quite often at work. I get lots of data from clients in Excel files that are formatted more for presentation than for analysis. I move the data over to Numbers (I hate working in Excel), clean it up into a simple tabular form, and export it as CSV for analysis with Pandas and other bits of Python.

In this process, it’s important that I keep the Numbers version of the table and the CSV version in sync. So I made a macro that saves the Numbers file and exports a CSV file at the same time. It’s just a single step with a short bit of JavaScript code.

Export as CSV

And here’s the code itself:

 1:  numbersApp = Application("Numbers");
 3:  // Get the top Numbers document and the path to its file
 4:  let topDoc = numbersApp.documents[0];
 5:  let topPathString = topDoc.file().toString()
 7:  // Make the path to the CSV file we're going to export to
 8:  let csvPath = Path(topPathString.replace(/\.numbers$/, ".csv"));
10:  // Save the Numbers file and export it as CSV
12:  numbersApp.export(topDoc, {to: csvPath, as: "CSV"});

I wrote this in JXA instead of AppleScript because of Line 8. Although some text processing is easy in AppleScript, most of it is a pain in the ass. And simple regex substitutions fall into the “pain in the ass” category. If it’s just one text substitution, you can cheat with a little do shell script, like this:

 1:  tell application "Numbers"
 2:    -- Get the frontmost Numbers document and a path to its file
 3:    set topDoc to front document
 4:    set topPathString to file of topDoc as text
 6:    -- Make a path to the CSV file we're going to export to.
 7:    set csvPathString to do shell script "sed 's/\\.numbers$/.csv/' <<< " & quoted form of topPathString
 9:    -- Save the Numbers file and export it as a CSV
10:    save topDoc
11:    export topDoc to file csvPathString as CSV
12:  end tell

The combination of sed and a here string in Line 7 is basically the same as Line 8 of the JavaScript, but you have to be very careful with the quoting anytime you delve into do shell script. The JavaScript is cleaner code, so that’s what I went with.1

Still, there is one part of the JXA code I don’t like: Line 12:

numbersApp.export(topDoc, {to: csvPath, as: "CSV"});

I just find it hard to get the hang of this syntax. There are three arguments to this function. One of them is passed in as a regular variable, but the other two have to be rolled into an object. That’s just weird.

I might find it easier to get over my argument hangup if Apple put more effort into its JXA documentation. And by “more” effort I mean “any.” Here’s the AppleScript dictionary entry for the Numbers export command. It’s written more or less as you would write it in AppleScript.

Numbers export in AppleScript

Now here’s the same documentation for the JavaScript version:

Numbers export in JavaScript

Does this look like JavaScript to you? It doesn’t to me. You have to have seen example code using similar methods to know that the first argument is the document and the second is an object with key/value pairs for all the indented parameters. And while we’re told that export is a method, we’re not told what it’s a method of. My first instinct is that it should be a method of the document, not the app.

Basically, the JavaScript documentation is the AppleScript documentation with a few regex substitutions applied. Change nouns to objects, verbs to methods, close up certain spaces, apply a few capitalization rules, and sprinkle in some colons. Boom! An afternoon project for an intern, not something that should come out of the most valuable company in the world.

This, I think, is one reason why AppleScript hangs on. There are tons of people who know JavaScript from web development, but Apple’s half-assed JXA documentation keeps them from transferring that knowledge easily to Mac automation.

  1. I didn’t write the AppleScript code until I started putting together this post and felt the need for a counterexample.