Applescript is dead, long live Applescript bridges
January 23rd, 2009
Well not exactly. Further to my previous attempt at making a workbench launcher I found that rubyosa is not the only way to interact with osx applications through ruby. rb-appscript is an alternative derived from appscript, which boasts 5 years production use amongst other things.
There are a few articles covering starting out with this stuff already, and the project's site has a good introduction, so I won't go over that. Rather, I'm going to show my results here, and the beginnings of a new workspace launcher.
References
- "Ruby, an AppleScript Alternative - Part 1" at MacDeveloperTips.com
- rb-appscript's own examples
- "Replacing AppleScript with Ruby" by Matt Neuburg, author of Applescript: The Definitive Guide
Dictionaries
When doing scripting work with an application you need to find it's dictionary, which provides a formal definition of things in the application that you can use in scripts. For example the terminal has from a standard 'suite' an 'application' container, containing 'windows', and it has the terminal specific 'tab' objects which are contained by the 'windows'. This is a round-about way of talking about your regular object composition pattern.
There are two ways (excluding rubyosa's, because it didn't work for the Terminal.app and we can't code in iTunes) to get at the dictionaries. One is with Apple's Script Editor application, by the File -> Open Dictionary dialogue. This is quite nice for looking around in a dynamic way. An alternative is provided by appscript's ASDictionary tool which will give you file output in various formats to refer to as and when. More importantly it gives you the method names in the correct rb-appscript format.
Issues
In the end I've come up with something pretty much the same as Solomon White, or at least come upon the same problems and used the same keypressing solution as previously.
It seems there isn't a pretty way of doing this at present with Terminal.app - Apple needs to fix the application before anyone can do anything cool with it. It doesn't allow you to create new windows or tabs dynamically, the work-around with keypresses is unreliable, can send them to the wrong process (Apple's event manager doesn't direct them to processes properly, despite being parameterised, it just gives them to the active application) and more importantly doesn't provide you a reference to the tabs or windows you've just created.
That means you can create windows and tabs (unreliably) but you can't use them directly, you have to fish through the entire pool of windows and tabs somehow to get references. This is fine if you don't have anything going on in other terminal windows elsewhere, but it's not great if you want to keep separate tasks going on in different terminal windows on different Expose spaces or similar.
Results
I'm not sure what to make of this really. The scripting bridges are nice, it's much more pleasant writing this stuff in Ruby than in Apple Script (which was not easy to learn, I don't know who they expect to use it really, it's hard for programmers and the majority of non-programmers will not be able to find the documents to learn it from, or even realise it exists). It would be nice for Apple to update the terminal to support these features, but it's going to impress a very small group of people. Now if it were open source...
Here's the code I've produced, but given the results I'm not going to finish it off until I need different processes. The work left to do is:
- string processing of project files, to get the commands array rather than the hard-coded one here
- wrapping into an application for toolbar use
specs/features writing to catch Apple fixing Terminal so that the code can be improved
terminal.rb
workbench_launcher
Created by Nicholas Rutherford on 2009-01-23.
Copyright 2009 Nicholas Rutherford. All rights reserved.
require 'rubygems' require 'appscript' include Appscript
class Terminal def initialize @term = app('Terminal') =begin TODO make it able to open a new window without creating 2 windows from a cold start =end @term.run end
def run(commands) commands.length.times { make_tab } tabs = @term.windows.first.tabs.get
commands.each do |command| @term.do_script(command, :in => tabs.pop) endend
def make_window # TODO rspec pendings for make window fixed by apple # window = term.make(:new => :window) @term.activate app("System Events").application_processes[ "Terminal.app" ].keystroke("n", :using => :command_down) end
def make_tab
# TODO rspec pendings for this tab creation stuff getting fixed by apple # window.make(:new => :tab) @term.activate app("System Events").application_processes[ "Terminal.app" ].keystroke("t", :using => :command_down) enddef windows @term.windows end end
commands = ["man ls", "man top", "man cd"] Terminal.new.run commands
Sorry, comments are closed for this article.