Replacing AppleScript with Ruby
Pages: 1, 2
Integrating Apple Events into Ruby
The true benefits of using Ruby instead of AppleScript as a base for sending Apple events to scriptable applications emerge when you consider the relative merits of the two languages. You shed the clumsy, tricky verbosity of AppleScript, along with its gaping linguistic holes (such as the paucity of string-handling abilities) in favor of such Ruby elegances as regular expressions, iterators, blocks, and true object-orientation—not to mention all the power of Ruby's vast built-in classes, libraries, and gems.
To illustrate this point, we'll rewrite the example from page 415 of my book to use Appscript. The rewrite will be fairly substantial, because my grasp of Ruby idiom at the time of its development was even more rudimentary than it is now. The script does something quite useful. First, it reads a text file, specified as an argument on the command line, and tallies the number of occurrences of each individual word (that is, it constructs a histogram of word usage). This is a typically simple Ruby task, thanks to hashes and regular expressions. Then it calls upon Microsoft Excel to draw a bar chart of the relative frequency of the 30 most common words in the file.
Here's the start of the script:
#!/usr/bin/env ruby
require 'appscript'
include Appscript
module Enumerable
def each_with_index1 # AppleScript indices usually 1-based
self.each_with_index {|item, index| yield item, index + 1}
end
end
We begin by inserting a convenience method into the built-in Enumerable module, allowing us to mediate easily during iterations between Ruby index numbers, which are 0-based, and AppleScript index numbers, which are 1-based.
Next, we open the specified file, construct the histogram, and close the file:
h = Hash.new(0)
open(ARGV[0]).each do |line|
line.scan(/\w+[-']*\w+/) do |w| # allow internal punctuation
h[w.downcase] += 1
end
end
h = h.sort{ |x,y| (x[1] <=> y[1]).nonzero? || y[0] <=> x[0] }
Words are hashed without respect to case. The regular expression used to define the notion of a word conceals some arbitrary but reasonable decisions: we ignore words consisting of only one letter, and we permit a word to contain a hyphen or an apostrophe.
The final line transforms the hash into an array of key-value pairs (that is, each word paired with the number of times it occurs in the document), sorted in the following order: words that occur more times in the file appear later in the array, and among words that occur the same number of times, a word that comes earlier in alphabetical order appears later in the array. The last 30 entries in the array are thus, in reverse order, the 30 most frequent words in the file. The order is intentionally reversed because this is the easiest way to construct the chart the way I want it.
Now we start talking to Excel. First, we bring Excel to the front and create a new workbook, capturing a reference to its first worksheet as a variable (w1):
excel = app('Microsoft Excel')
excel.activate
excel.make(:new => :workbook)
w1 = excel.worksheets[1]
Next, we enter the last 30 entries in the array as data in the worksheet. Words are placed in the Excel range A1:A30 (the most frequently used word thus appearing in A30), with their frequencies beside them in the range B1:B30. This is the iteration for which we created Enumerable#each_with_index1 earlier:
h[-30..-1].each_with_index1 do |pair, row|
pair.each_with_index1 { |val, col| w1.rows[row].cells[col].value.set(val)}
end
The following screenshot shows the data as entered into Excel when the script was run against a partially completed first draft of this article:

Figure 1. The data as entered into Excel
(The article was written in HTML, and the code does nothing to eliminate tags, so pseudowords such as "pre" and "code" are included in the list.)
At last we are ready to construct the chart.
excel.ranges["A1:B30"].select
excel.make(:new => :chart_sheet, :at => excel.active_workbook.start)
The remainder of the script is simply a matter of formatting the chart the way I want it—namely, a bar chart with the title "30 Most Frequent Words," with no legend, with data labels on the bars (so that each bar ends with the word it represents), and with no gridlines on any axis. Excel's AppleScript commands and object model are effectively a wrapper around its Visual Basic commands and object model, and are peculiarly unidiomatic. So, the resulting code is rather odd-looking, but this is due to Excel, not to Appscript.
c = excel.active_chart
c.chart_type.set(:bar_clustered)
c.has_title.set(true)
c.chart_title.caption.set("30 Most Frequent Words")
c.has_legend.set(false)
c.apply_data_labels(:type_ => :data_labels_show_label, :legend_key => false)
[:category_axis, :series_axis, :value_axis].each do |axt|
[:primary_axis, :secondary_axis].each do |ax|
begin
x = c.get_axis(:axis_type => axt, :which_axis => ax)
x.has_major_gridlines.set(false)
x.has_minor_gridlines.set(false)
x.has_title.set(false)
c.set_has_axis(:axis_type => axt, :axis_group => ax, :axis_exists => false)
rescue
end
end
end
The two nested each do loops are a way of iterating through every possible axis of the chart (because Excel doesn't let us ask for all of them at once). In reality, of the resulting six axes, only two are capable of having their has major gridlines property set, but I am too impatient to worry about which ones these are. So I wrap the interior of the loop in a begin...rescue...end block, which is the Ruby equivalent of an AppleScript try block. If an axis doesn't have gridlines, a runtime error occurs; the rescue block is empty of code and blithely ignores the error, and we proceed to the next iteration.
Here's the resulting chart, corresponding to the data from the earlier screenshot:

Figure 2. The resulting chart
Conclusions
Finding an alternative way of sending Apple events is not merely a matter of petulant impatience with the AppleScript language, it may very well be the path to the future. The Apple event/AppleScript mode of scripting applications dates back well over a decade and a half to Macintosh System 7. Apple events themselves are unlikely to go away any time soon, and in many ways they don't deserve to. An Apple event is a remarkably sophisticated way of requesting and communicating information, and Cocoa Scripting is improving with each major Mac OS system release, making it easier and easier for Cocoa programmers to wrap scriptability around their applications. AppleScript, on the other hand, is implemented by what is probably the oldest code remaining in any corner of the system. That code—which is probably very difficult to revise substantially at this point—was originally intended to run in a tiny RAM space, and the language that it implements made a number of assumptions about what would make scripting "easier" for the naive user, who was presumed to be afraid of the notion of doing any programming.
But the user of today is not like the imagined user of the "appliance" Macintosh of 1991. The developer tools are free. "Ordinary" users are churning out Dashboard widgets written in JavaScript. The Unix heritage of Mac OS X has brought into the user base large numbers of users familiar with Perl and shell scripting; modern Web applications run on PHP; and of course the popularity of Ruby itself is increasing at what seems an exponential rate. In the near future, the Microsoft Office applications are slated to lose Visual Basic support on the Mac, and VBA experts will be seeking an alternative language in which to recast their existing macros. On Windows, furthermore, there is already a bridge between Ruby and scripting the Office applications, so Windows refugees would naturally look for an equivalent on Mac OS. All these users are mature and experienced enough to recognize that scripting is programming and that a programming language is acquired through study. When confronted with AppleScript as the dominant language for scripting Mac applications, they may naturally feel a certain disappointment, not to mention disbelief.
In this environment, and in the spirit of "the more, the merrier," adding to the scripter's toolbox the ability to send Apple events easily from Ruby can only be a good thing. Appscript is a remarkably well-implemented library, by a developer with a deep, highly technical, and altogether practical understanding of Apple events and AppleScript. It has reached a sufficiently mature stage of development to be reliably usable; what it needs now is more users and more feedback. I hope this article has encouraged you to give it a try.
Matt Neuburg is the author of O'Reilly's "AppleScript: The Definitive Guide," "REALbasic: The Definitive Guide," and "Frontier: The Definitive Guide," and is a former editor of MacTech magazine.
Return to the Mac DevCenter.
Showing messages 1 through 34 of 34.
-
passing requests from applescripts to rb-appscripts
2008-04-08 20:43:43 gauvins [Reply | View]
Simply:
tell application "Terminal"
do script "ruby test2.rb /Users/gauvins/Desktop/test.txt"
end tell
--
see below for some context. In a nutshell, direct calls from the Script Editor do not execute properly because paths are broken.
-
passing requests from applescripts to rb-appscripts
2008-04-08 10:56:02 gauvins [Reply | View]
Thanks for an excellent post. I've been able to run your example without problem, from the Terminal.
I am unable, however, to figure how an applescript can call an rb-appscript.
Terminal: ruby test2.rb /Users/gauvins/Desktop/test.txt (does work)
AppleScript:
set the_params to “/Users/gauvins/Desktop/test.txt”
do shell script “ruby test2.rb ” & the_params
does not work. Including variants
Any idea?
(I am not a developper at all. Just a poor soul trying to automate a very tedious task :) -
passing requests from applescripts to rb-appscripts
2008-04-08 11:21:34 mattneub [Reply | View]
Hi, gauvins -
I'm not sure why you'd want to do this; the whole point of my article here is that you can just start in Ruby and stay in Ruby, and never use any AppleScript code at all. However, my book explains about calling Perl, Ruby, etc. from AppleScript and vice versa. Here is a simple example.
Ruby file /Users/mattneub/Desktop/test.rb:
puts $*[0].reverse
AppleScript code:
set f to "/Users/mattneub/Desktop/test.rb"
set s to "ruby " & quoted form of f & space & quoted form of "Hello, World!"
do shell script s -- result: "!dlroW ,olleH"
Here is another example.
Ruby file /Users/mattneub/Desktop/test.rb:
require "appscript"
include Appscript
puts app('Finder').name.get
AppleScript code:
set f to "/Users/mattneub/Desktop/test.rb"
set s to "ruby " & quoted form of f
do shell script s -- result: "Finder"
That should get you started.
-
passing requests from applescripts to rb-appscripts
2008-04-08 16:23:08 gauvins [Reply | View]
Thanks Matt for the (quick) follow-up
first snippets work well. Second do not. I get the following error message :
/Users/gauvins/Desktop/test.rb:1:in `require': no such file to load -- appscript (LoadError)
from /Users/gauvins/Desktop/test.rb:1
---
I stumbled upon your post searching for a way to do exactly what you did in your example (count instances of words in a list). The (few) applescripts examples I could locate seemed very inefficient. An Applescript based app chokes on the large file I used to test the procedure (close to 1G ...).
I understand that Rb-script could be immensely superior, but I would rather try to use your code as some kind of external function, for the moment at least. Maybe this summer I can find the time to rewrite.
-
Accessing the clipboard via rb-appscript
2007-03-01 16:19:31 bbebop [Reply | View]
Matt, great article. I've been playing around with appscript by rewriting an existing applescript that contains the following code:
set the clipboard to "some text"
Frankly, not sure what app controls the clipboard. Thanks! -
Accessing the clipboard via rb-appscript
2007-03-01 17:49:48 mattneub [Reply | View]
Frankly, not sure what app controls the clipboard
See my book! "set the clipboard" is an osax command. The article tells you how to access osax commands. So e.g. osax.set_the_clipboard_to("howdy"). -
Accessing the clipboard via rb-appscript
2007-03-01 23:33:05 bbebop [Reply | View]
Thanks, Matt. That helped. Now I must have my copy of Frontier: The Definitive Guide around here somewhere. Never could get next to Applescript and miss Frontier. Maybe Ruby will be the charm?? -
Accessing the clipboard via rb-appscript
2007-03-02 16:11:05 mattneub [Reply | View]
Indeed. It was Frontier that taught me that there was a better way to construct and send Apple events. And Ruby does have some features that are pleasingly similar to Frontier's UserTalk, and rb-appscript is also quite reminiscent of the way UserTalk did Apple events.
-
Eudora?
2007-03-01 05:51:58 angusm [Reply | View]
Good article - thank you. And I like the idea of using Ruby rather than AppleScript.
Unfortunately, it seems there's no escaping the "can't get there from here" frustrations of OSA, and errors that come back are rendered even more cryptic.
The first thing I wanted to do was to get the selected text from the first window in Eudora, but even identifying the first window seems impossible.
app('Eudora').windows[1]
app('Eudora').application.windows[1]
app('Eudora').window[1]
They all seem to fail with 'unknown property, element or command', making it hard to decide how to go on.
eudora = app('Eudora')
sub = eudora.messages[1].subject
puts sub
doesn't throw any errors, but it doesn't seem to give back anything useful either.
Has anyone had any success driving Eudora from rb-appscript, or is the underlying AETE too broken to allow it?
-
Eudora?
2007-03-01 07:35:43 mattneub [Reply | View]
Pls see my book, which gives lots of examples of Eudora's scripability brokenness. You cannot expect rb-appscript or any scripting milieu to fix this. In AppleScript, you cannot say:
tell application "Eudora"
get window 1
end tell
Eudora just whines at you when you say that. So you can't say it in Ruby either.
As for your second example, it would help you to take time to read what I said in the article, or at least the rb-appscript docs.eudora.messages[1].subjectis a reference. To evaluate it, useget:
puts app('Eudora').messages[1].subject.get
works fine.
-
Can't include Appscript
2007-03-01 00:36:33 stiang [Reply | View]
Great article, but I got stuck when I wanted to try the examples. I installed via RubyGems, so I thought this would work:
irb(main):005:0> require "rubygems"
=> true
irb(main):006:0> gem "rb-appscript"
=> true
irb(main):007:0> include Appscript
NameError: uninitialized constant Appscript
from (irb):7
from /usr/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:234
Am I doing something wrong? -
Can't include Appscript
2007-03-01 07:05:12 mattneub [Reply | View]
Hi, stiang - I don't have a good answer. With RubyGems, you're on your own. Try the installation method I described and see if that works better for you. The article describes the installation method that I was able to get to work. It doesn't talk about methods that I wasn't able to get to work.
-
Good idea, but...
2007-02-28 14:07:16 pauls101 [Reply | View]
the problem with Applescript isn't primarily the language. While I would certainly prefer to work in Ruby, it's not a cure all.
Applescript support is extremely difficult to do well, being very complex and poorly documented; those who try beyond the very basics (what's built into Powerplant, for example) often don't get it right or complete; very few indeed fully test and debug, let alone document beyond the frustratingly useless Dictionary resource. Last time I tried a few years ago, Apple's notoriously buggy Finder drove me to despair: of the 6 ways a given job might logically be accomplished: 2 work correctly most of the time; 3 fail with bogus errors or refuse to compile for no obvious reason; 1 fails "silently" and appears to do nothing. Debugging was limited (as in non-existent) except for a few third party programs that didn't work very well either.
Using Applescript usually means spending a long time learning the idiosyncracies of a target application, with little application elsewhere. A different scripting language might make it easier (I had high hopes for the Javascript OSA system a while back) but it won't fix all the things that make AS suck rocks. -
Good idea, but...
2007-03-01 07:50:03 mattneub [Reply | View]
pauls101: Yes, my book is largely about the fact, that the Dictionary is usually frustrating and that AppleScript programming ends up being mostly guesswork. (In fact, my book includes a long Appendix showing me guessing and guessing in order to develop an actual script, and explaining to the reader that this really is how you have to work and think in order to do this stuff.)
Having said that, though, I must say that I'm of two minds about where Apple should go from here. Would it make sense also to throw out Apple events and the object model of properties and elements? It's an attractive idea emotionally (one thinks, let's just use F-Script or some similar alternative). But logically, there is so much invested in existing scriptable apps, and an Apple event, if used properly, is such a powerful means of communication and query, that one suspects that in reality, as I say in the article, Apple events are not going away any time soon. I've seen some pretty good arguments (including some, I think, from Hamish) as to why Apple events and the object model (and the dictionary) are really not so bad.
On the other hand, let's go back to your example. Why does the Finder's scriptability suck? It's because adding AppleScript scriptability to an app in such a way that the result doesn't suck is really, really hard - so hard that Apple can't do it right. It always has been hard. So, that's an argument for your view ("let's throw out the bathwater and the baby - please!").
-
A quick note for PPC users
2007-02-28 10:37:51 Macam [Reply | View]
If you're following Matt's tutorial, you can't install from source as he does if you're on a PPC machine. To install it on a PPC machine, you'll need to download the .dmg and run the PPC .pkg installer. This is noted in the README file of the .dmg in the rb-appscript-0.3.0 folder. From that file:
"Please note that the version of Ruby included with Mac OS X 10.4 is missing the header files needed to build appscript on PPC-based Macs. You can avoid this issue by installing the latest version of Ruby from <http://www.ruby-lang.org/en/downloads/> and updating your shell login profiles to suit."
Just wanted to pass along the heads up. -
A quick note for PPC users
2007-03-01 07:07:56 mattneub [Reply | View]
If you're following Matt's tutorial, you can't install from source as he does if you're on a PPC machine
In the article I describe installing from source, and I'm on a PPC machine. Perhaps the difference is that I've also installed Ruby 1.8.5? -
A quick note for PPC users
2007-02-28 12:43:35 hhas [Reply | View]
Just to clarify:
You can install rb-appscript into Tiger's Apple-installed Ruby 1.8.2 using the binary .pkg installers in the .dmg. This avoids the missing header problem on PPC; there's also an i386 installer included for convenience. Folk who would like to try out appscript but don't have gcc installed will also find this the easiest option.
You can install your own copy of Ruby (the latest is 1.8.5) and then install rb-appscript into that from source or .gem without any problem, as long as you've got gcc installed. (If gcc isn't installed, installing any version of Developer Tools should provide a suitable copy.)
Apologies if my readmes caused any confusion.
has
p.s. Great article, Matt!
-
Xcode integration
2007-02-28 08:42:41 damonhoxworth [Reply | View]
What are the abilities as far as using appscrpt with Xcode?
Am I able to create droplets, or Studio like (standalone) apps with it?
Thanks for the great article.
d -
Xcode integration
2007-02-28 11:49:33 hhas [Reply | View]
For building Studio-based applications you need a full OSA language component for that (appscript is just an Apple event bridge), plus a bit of judicious hacking of XCode to make it use Ruby instead of AppleScript.
There is a RubyOSA language component available at http://homepage.mac.com/philip_aker/osa/osa.html but it provides only limited functionality (load, save, compile and run), so isn't powerful enough for building Studio apps, though you can create Script Editor applets with it. For more advanced projects, I'd second Damon's suggestion of RubyCocoa.
HTH
-
Clarification on python
2007-02-28 07:19:26 sinjin [Reply | View]
Eye-opening article, thanks.
I'm wondering if what you discussed applies to python with appscript as well. I.e. Am I best sticking with python since I already know it, or does Ruby bring advantages to appscript that are worth my while (as a very occassional scripter).
Thanks again, Mark -
Clarification on python
2007-02-28 11:40:58 hhas [Reply | View]
I'd say just go with whichever language you find most comfortable. Both appscripts provide the same level of functionality, and the APIs are pretty much identical modulo the variations needed to accomodate the different languages' strengths and weaknesses (e.g. Python has better keyword argument support, Ruby has a proper Symbol class).
Python appscript has a few nice add-ons, in particular its built-in help() method, which I've still to implement in rb-appscript. However, you can already get most of the same functionality via the osadict tool which is bundled with py-appscript and provides support for Python, Ruby and AppleScript, with more languages planned.
HTH
-
Vs rubyosa?
2007-02-28 00:56:59 mezza9 [Reply | View]
Me being lazy, but what's the difference between "Rubyosa":http://rubyosa.rubyforge.org/ and rb-appscript? -
Vs rubyosa?
2007-02-28 12:29:57 hhas [Reply | View]
Both are Apple event bridges for Ruby; however, appscript is a mature, pretty much finished product that provides about the same level of flexibility, functionality and application compatibility, while RubyOSA currently isn't/doesn't. If you want, I posted a slightly longer comparison to c.s.m.p. about a month ago:
http://groups.google.com/group/comp.sys.mac.programmer.help/browse_frm/thread/3db9a78006675e0f/06438b089d491aeb
The latest RubyOSA has addressed some of the easier issues since then (keyword arguments, osax and remote scripting support), but the rest still stand.
HTH
-
Performance?
2007-02-27 10:16:58 DaveLentz [Reply | View]
So what's the performance like, compared to the same Applescript implemented as an AppleScript script, a compiled AppleScript app, and as implemented in Ruby with Apple Event support?
Neither AppleScript nor Ruby are viewed as high-performance languages (admittedly, work is ongoing toward making Ruby faster). But the real reaason -- in the absence of performance concerns/constraints -- to select one scripting language over another is the ease with which it can be implemented and maintained.
While I routinely mix AppleScript with other scripting languages via the osascript facility in the shell environment and the AppleScript "do shell script" construct, I find that the best thing about AppleScript is that when coming back to a complex bit of code written with a lot of time between now and then, the AppleScript is always the easiest to understand what is going on in the code.
But I'm certainly not averse to extending the utility of Ruby in my scripting toolbox.
Great Stuff, -
Performance?
2007-03-01 07:18:19 mattneub [Reply | View]
DaveLentz:
You raise two unrelated issues.
First, there's the question of actual timings. I haven't done formal tests, but rb-appscript sure feels a faster to me than running an AppleScript script. Of course most of the time in a real script is spent in communicating with and waiting for the target application, so the source language doesn't matter. But quite aside from the AppleScript compilation issue, AppleScript is full of hidden speed traps. Just the other day I wrote an AppleScript script that required accessing information in a List of 2000 Lists; the mere size of the list caused the script to bog down, plus the lookup involved looping through the whole LIst because AppleScript has no hashes. So basically my response would be that as soon as you need to get anything done other than talking back and forth with the target application, Ruby is faster and more liberating because you don't have to dance around and tweak in order to get decent performance.
As for maintainability of complex code, I'm sorry, I don't agree at all. I can't maintain or even read an AppleScript script of any size. Ruby's object-orientation is all about maintainability, plus, as I say in my book, I find AppleScript's verbose English-likeness, and the bizarre verbal dances you have to do in order to perform simple string manipulation, completely illegible. -
Performance?
2007-02-28 11:28:15 hhas [Reply | View]
Performance varies a bit depending on what you're doing. For example, commands that move a lot of complex data between the application and Ruby tend to be a bit slower, since the conversions are largely done by Ruby code whereas AppleScript is pure C. My own feeling is you can probably expect appscript code to run maybe 30-90% as fast as the equivalent AppleScript code, which in real-world use seems to be "fast enough". Also, Ruby is far, far better at regular programming tasks than AppleScript is, so pretty much everything else needs less code and runs faster than it would in AppleScript, more than balancing things out.
The other thing to bear in mind is that the most significant performance factor is usually the time it takes applications to evaluate queries and respond to commands - something that affects all users equally. While things have improved over the years, the focus of Apple event-based IPC has always been more on power than raw speed. The appscript manual includes some useful advice on eking out the best performance when carrying out more complex tasks.
HTH
has
--
http://appscript.sourceforge.net
http://rb-appscript.rubyforge.org
http://appscript.sourceforge.net/objc-appscript.html






I just wanted to add for Excel 2008 I had to change the line
excel.make(:new => :chart_sheet, :at => excel.active_workbook.start)
to
excel.make(:new => :chart_sheet, :at => excel.active_workbook.beginning)
But apart from that everything worked as described in the article.