Mac OS X services in Ruby with Bellhop
October 7th, 2007
So, I was finishing off the list of programs I use on my Mac, which started off just as bullet-pointed plain text (I typed each program in as I installed it). This being the interweb, I figured it'd be a bit flash if you could click on the program names and be shown their web sites.
Half way down the page, doing this by hand, I got very bored.
Goal: I want to select a URL, select Shiira > Services > Markdown URL and have the title of the page at that URL used as the name of a Markdown link. (For some reason, I started using Markdown, not Textile.)
Easy? Well it is if you know how.
First go
First up was TextWielder. This lets you do find-and-replace pattern matching on highlighted text. Except, you have to write the replace bit in JSP(!). However, you can also make it run the replace bit as an AppleScript, so I started there - AppleScript is for people who failed VB.net 101, so seems like an easy route.
About two hours later, I realised there is no simple way to fetch a web page in AppleScript and extract the title tag (or any documentation written this millenium (seriously!) that might explain how.
Second go
Sticking with the TextWielder thing, I decided to do it in JSP. That's ok, I use a sheep strapped upside down in a crane-supported harness to wash my car, so I'm used to tools being not quite right for the job.
Man alive, now I remember why I stopped using Java!!!
Another half hour of pain and I decided to cut my losses. I knew how to do the actually HTML processing bit in Ruby in about 3 lines, so I adjusted my search keywords and found...
Third go - Bellhop!
Bellhop lets you write Mac OS X services in dynamic languages, which is a slightly more appropriate range of tools than JSP. You get to pick from AppleScript (maybe not), Python, Perl, F-Script and... Ruby!
There's a slight obstacle, namely that unlike, say, svnX, Bellhop does not allow you to specify the location of the binaries it calls, and I wanted to use my MacPorts install of Ruby so I could install Hpricot. Before any of this will work, then, you have to replace the Apple Ruby with the MacPorts version. Open a Terminal window and do this:
$ cd /usr/bin
$ sudo mv ruby ruby-apple
$ sudo ln -s /opt/local/bin/ruby .
Next, I used a couple of gems, namely Hpricot, a fast and delightful HTML parser, and HTMLEntities to catch any wacky characters:
$ sudo gem install hpricot
$ sudo gem install htmlentities
The final service handler, which you can install in a new Bellhop file, looks like this (haven't yet worked out how to do Ruby syntax highlighting in Mephisto - I have now :) ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
require 'rubygems' require 'hpricot' require 'open-uri' require 'htmlentities' def title(page_location) doc = Hpricot(open(page_location)) entity_coder = HTMLEntities.new entity_coder.decode((doc/:head/:title).inner_html.strip) end def runService(aPasteboard) url_string = Pasteboard.readData(aPasteboard, NSStringPboardType) markdown = "[#{title(url_string)}](#{url_string})" Pasteboard.declareTypes(aPasteboard, [NSStringPboardType]) Pasteboard.writeString(aPasteboard, NSStringPboardType, markdown) end |
And that's it! If I'd known to try that first, I would've been done several hours ago. Now I know how, though, I might find myself writing more services in future. Just not in AppleScript.

Leave a Reply