Blogging with TextMate, and using AppleScript and JavaScript to ease the pain

February 15, 2010

Let me begin by saying that I endeavored initially to get this working with Google Chrome (my main browser these days), but because Chrome's AppleScript support currently is very minimal, it just wasn't possible. In light of that I chose to at least proof-of-concept the idea with Safari/WebKit so that I can later use this post to inform my solution for Chrome (and so others can take advantage of this now if they happen to use Safari). (Update: I've come up with a solution for Chrome.)

A few weeks ago I decided I had to have support for Markdown syntax highlighting in my blogging client, which at the time (and for many years prior) was MarsEdit. After coming to terms with the fact that such highlighting just wasn't going to happen in MarsEdit (any time soon), I set out to find another solution, and quickly remembered that TextMate (which may be my favorite application of all time) came out with a blogging bundle many years ago. After watching the screencast, running some tests and creating a couple of blogging templates, I very quickly was using TextMate to write and publish.

Doing all of my blogging with TextMate these past few weeks has been great, except for one terribly annoying thing, namely the inability to quickly conjure up a "linked-list"-type post. I was having to manually -- *gasp* -- create these (usually) quick, simple posts. I don't necessarily need speed when starting long-form blog posts like the one you're currently reading, but for the more frequent linked-list posts I definitely do. I need/want something that amounts to little more than a keyboard shortcut. MarsEdit has spoiled me over the years with its simple bookmarklet that grabs the data I need (i.e., page title, page URI and any currently-selected text), drops it into a new post (see my MarsEdit template below, formatted for Markdown) and populates the Title field with the title of the page.


> selectedText

(Via []().)

Obviously then I wanted an equal or less amount of friction when doing similar operations via TextMate. (For long-form stuff I created a new blogging template that has all the fields I need for a non-linked-list post, so I can just go to File > New From Template > Blogging > Blog when I want to start writing a longer piece). While I was able to get some of the aforementioned bookmarklet's functionality in Chrome via a combination of (a service using) Automator and AppleScript (i.e., Service receives selected text in Google Chrome together with some AppleScript to act on the selected text), it was limited to just the highlighted text (i.e., no page title, URI, etc.), which made the process only slightly better (if not worse) than just doing everything manually. It was at this point I resigned trying to get this to work with Chrome and moved on to Safari/WebKit.

Because Safari's AppleScript support allows you to run JavaScript, getting the three data elements I wanted was quite easy. To wit:

tell application "WebKit"
    set selectedText to (do JavaScript "(getSelection())" in document 1)
    set pageURI to (get URL of document 1)
    set pageTitle to (do JavaScript "document.title" in document 1)
end tell

(Note: if you use Safari you'll want to change "WebKit" to "Safari.")

Once I had the critical information, I needed to figure out how to get all of it into a new post within TextMate. If you watched the blogging bundle screencast or previously have blogged with TextMate, you know that various post-specific elements are set in a "header" at the top of each file (where each file corresponds to a particular post). For my linked-list posts, this header looks like the following:

Pings: Off
Comments: Off
Category: bits
To automate the inclusion of these headers in a new file (to be created when the script is set in motion), I had to create a new template for the blogging bundle (Bundles > Bundle Editor > Show Bundle Editor). (You can use the blogging templates that exist already as guides to creating your own.) After setting that up, I whipped up the following code to "click" on the correct menu item within TextMate so that a new file gets created using the new template.

tell application "TextMate"
    tell application "System Events"
        tell process "TextMate"
            tell menu bar 1
                tell menu bar item "File"
                    tell menu "File"
                        tell menu item "New From Template"
                            tell menu "New From Template"
                                tell menu item "Blogging"
                                    tell menu "Blogging"
                                        click menu item "Template"
                                    end tell
                                end tell
                            end tell
                        end tell
                    end tell
                end tell
            end tell
        end tell
    end tell
end tell

("Template" in the above code is whatever you named the template you created.) It's ugly, I know, but it gets the job done (and besides, I ended up not using it; keep reading). Now, each time I invoke the script I get a new file with proper headers and set to the desired bundle type (in my case, Markdown). However, I ran into a slight problem when attempting to insert the title, URI and selected text at their correct positions within the file (e.g., pageTitle should be inserted as so: "Title: pageTitle"). I tried using AppleScript to move around within the document (using variations on the code below, which was to move the cursor to the right by seven spaces), but no dice. I struggled for a while with this and ultimately just had to convince myself that a solution probably wasn't worth the time (though I'm certain it's possible).

repeat 7 times
    keystroke (ASCII character 129) using command down
end repeat

In light of my unsuccessful cursor positioning, I attempted to tackle the problem from another angle, and decided I could just create the headers with AppleScript instead of relying on the template from the blogging bundle. At this point there were three ways to move forward: 1) create a blank template and use it as described above (minus headers); 2) use TextMate's built-in URL scheme that allows you to open local files with TextMate from within other applications (e.g., Safari via AppleScript); or 3) use AppleScript to cause TextMate to open a local file directly.

Option 1 should be fairly self-explanatory given the discussion so far. For option 2 you'll want to tell Safari to run the following command within your script.

open location "txmt://open/?url=file://~/path/to/local/file"

The "local" file referenced above is one you will have created and saved, and to which you will have assigned the desired bundle type so that the syntax highlighting is correct. You'll want to keep this file empty, because each time you invoke your script the proper headers and other relevant information will be auto-inserted. In my case, I never need to save this file (i.e., when creating a new post) because I'm only using this setup for my linked-list posts, which I publish almost as soon as I create; given this, the file always is blank, and therefore ready to receive the stuff I want to insert.

Option 3 is the route I chose for my implementation, because it's the most straightforward (and because option 2 creates a new blank tab that I didn't feel like figuring out how to kill). With option 3 you simply tell TextMate to open the local file.

open "/path/to/local/file"

At this point all that was left to figure out was how to actually insert the headers and all of the information grabbed using JavaScript (discussed at the beginning of this piece). It ended up being rather trivial and is shown below as part of the "complete" AppleScript file, which you should be able to use outright (being sure, of course, to change the open file path).

tell application "WebKit"
    set selectedText to (do JavaScript "(getSelection())" in document 1)
    set pageURI to (get URL of document 1)
    set pageTitle to (do JavaScript "document.title" in document 1)
end tell

property LF : ASCII character 10

tell application "TextMate"
    open "/path/to/local/file"
    set post to "Title: " & pageTitle & LF
    set post to post & "Slug: " & LF
    set post to post & "Pings: Off" & LF
    set post to post & "Comments: Off" & LF
    set post to post & "Category: posts" & LF & LF
    set post to post & "[" & pageTitle & "]"
    set post to post & "(" & pageURI & ")." & LF & LF
    set post to post & "> " & selectedText & LF
    insert post
end tell

("LF" corresponds to the Unix linefeed character.)

I launch the script using FastScripts (specifically, I use option-b), but obviously you can just access the Script menu in the menu bar if you're OK with being a little less efficient. ;)

As I noted at the beginning of this piece, this solution works beautifully in Safari/WebKit, but I currently use Chrome. Hopefully Chrome's AppleScript support soon will be as robust as Safari's, but something tells me that that probably is the last thing on the team's mind right now. If and when sufficient support is there, I'll be sure to update this code and this post.

John Siracusa looks back with a decade's hindsight at his early reviews of (mainly developer previews of) Mac OS X#

What an enjoyable and educative stroll down what is, for me at least, a faux memory lane. You see, the reviews John revisits here are ones I've never read, because I didn't hop on the Mac OS X bandwagon until spring 2003, when I was finishing up my computer engineering degree. I decided early in that last semester that as soon as the semester was over I would drop Linux (which had been my obsessed-over baby for many years at that point) and give Mac OS X a serious evaluation. I haven't looked back. (True to form, I actually couldn't wait until school ended before ordering a Titanium PowerBook G4.)

As any Mac OS X fan knows, John's reviews of point releases are required reading, and something I always look forward to when a new version is imminent.

Microsoft's creative destruction#

At Microsoft, [internal competition] has created a dysfunctional corporate culture in which the big established groups are allowed to prey upon emerging teams, belittle their efforts, compete unfairly against them for resources, and over time hector them out of existence. It's not an accident that almost all the executives in charge of Microsoft's music, e-books, phone, online, search and tablet efforts over the past decade have left.

Steven Strogatz on math, from basic to baffling#

[Over the next several weeks] I'll be writing about the elements of mathematics, from pre-school to grad school, for anyone out there who'd like to have a second chance at the subject -- but this time from an adult perspective. It's not intended to be remedial. The goal is to give you a better feeling for what math is all about and why it's so enthralling to those who get it.

Awesome! (Via Jason Kottke.)

Parkour motion reel#

The coolest, most imaginative video you'll watch this week. I've never seen anything like it.

The good enough revolution: when cheap and simple is just fine#

The world has sped up, become more connected and a whole lot busier. As a result, what consumers want from the products and services they buy is fundamentally changing. We now favor flexibility over high fidelity, convenience over features, quick and dirty over slow and polished. Having it here and now is more important than having it perfect. These changes run so deep and wide, they're actually altering what we mean when we describe a product as "high-quality."

Who makes the best plain white t-shirt?#

Best question ever? Maybe. As some know, I've been on the hunt for the best, plain t-shirt for about a decade now (and believe me, when I come across one I really like, I buy 10 or more of them). (For what it's worth, Abercrombie's are really nice, but only come in three colors.)

The iPhone SmartBase

January 31, 2010

The unite/Balmuda SmartBase (buy) is an absurdly simple iPhone stand, as can be seen from the picture below (and from the others on the product page). It's a single piece of semi-hard rubber, with a channel underneath for the syncing/charging cable, which channel allows the cable to stay connected to the stand and the stand to remain flat against the table. But for the pics and video I saw beforehand, I probably would have been a little upset to find such a simple piece of kit inside the package.

unite SmartBase

The iPhone fits the stand well, and when within it is tilted at an angle that makes for easy texting, etc., even when the phone is kept near the back of your desk.

My only real criticism is that the base is way too light; it weighs next to nothing. Not only would more weight trick you into thinking that your $18 (it was $24 when I bought mine) went to something other than straight profit, but it also would make the stand a bit more practical.

At its current weight there's really no way to plug the cable into the phone and then place the phone in the stand with one hand; you have to use your other hand to keep the stand in place because it tends to slide around (or even come off the table a bit) as the attached cable tugs at it. In fact, if you use only enough excess cable as is needed to stow it under the stand when not in use (as shown in the video on the product page), then the tension from the cable (caused by it being bent to plug into the phone) is enough to raise the stand off the desk.

I'd love to say more about the SmartBase, but, well, look at it — it's a piece a rubber.  ;)

(For those wondering, before the SmartBase I was using a Just Mobile Xtand.)

The truth about robotic's uncanny valley#

[I]n person, most robots, particularly ones designed to interact with humans, are simply not scary. They're bumbling and a little helpless. Like a pet or a child, you cut them slack. In the most generalized, vaguely accurate way, the uncanny valley might apply to the corpse-eyed CG ghouls of The Polar Express or the recent animated Christmas Carol. But when it comes to robots, it's a largely hypothetical chasm, a term that only partially describes a fleeting, cognitive glitch that has no bearing on the way humans will live with machines.

It seems to me the author is being terribly short-sighted; sure, today, most robots we interact with are as described (i.e., not even remotely close to the valley's left-handed precipice), but, uhh, it's just a matter of time before human-like robots are able to accurately mimic our physical and social cues, and along this road, at various progressional stages, there no doubt will be awkward periods — however fleeting — where many facets of the human condition will be questioned, tested and ultimately altered.

I especially liked the following two comments on the article:

(1) I understand the uncanny valley lies in the difference between knowing and feeling. That our brain feels awkward when an intellectual idea (e.g.,, "this is a robot") contradicts a feeling (e.g., "I could have sex with this").

(2) If we really want to evolve as a species, and be able to take advantage of the amazing technologies that are coming down the pike - then maybe the first changes shouldn't be about robots becoming friendlier. Or AI becoming human-compatible. Maybe it's us that has to change. Maybe, along with the apparent strides we've made in conquering racism, we need to also reconsider our automatic reflex of repulsion, when gazing upon all things that are different, or weird. Whether those things are human beings of a different pigment, robots who don't fit our standards of human-like, or strange looking aliens in the future - maybe the first steps in approaching a truly advanced technological era, lies first in changing ourselves, our reactions, our snap judgements.