Today is my second day at Hacker School, and I decided to set up a little bit of tooling for blogging about what I do here. The first tool I set up (following the recommendations of many Hacker Schoolers and alums) was Octopress, a static site generator designed for GitHub Pages and implemented atop Jekyll. (The page you’re reading right now is Octopress-generated.) I followed the admirably thorough Octopress documentation for installation, initial configuration, deployment with Github Pages, and theme customization1. But I wanted even more convenience. So, I’m here to introduce you to the
blog command (the same one I used to write this very post).
davidad@zayin ~/octopress> blog Enter a title for your post:
blog is a
bash script, pretty specific to my own setup (vim, chrome, OSX), but it could be adapted to other environments.
blog can create a post using Octopress’
new_post Rake target (and you can specify a title on the command line if you want), then it opens
vim in sort of
git commit-ish fashion, with your cursor on the last line ready to press
o and start typing your post, and with magical deployment when you
:wq2. It also implements
blog deploy (runs both generate and deploy),
blog delete, and editing existing posts. Most importantly, whenever editing the script sets up a keybinding for
C-g that saves your draft post and refreshes the local preview in a Chrome window. It does this even if you don’t have a tab open to refresh, but it also won’t open a new one if you do. And it keeps your
vim window in the foreground. How does this work? You might expect that Chrome has a nice command-line remote interface for exactly this sort of thing. Sadly, that is not the case. However, Apple has had the foresight to allow command-driven automation of actions which can typically only be carried out graphically. Sadly again, that mechanism is AppleScript, a historical relic of a programming language.
In this snippet,
$1 is going to get replaced with the site’s top-level URL (like
http://localhost:4000/ for the local preview server, or
http://davidad.github.io/ for the deployment).
$L2 are placeholders for two actions that we might not always
The interface to AppleScript is the
osascript command, which accepts an AppleScript file as its argument5. So, the first big chunk of the
blog script is dedicated to producing script files. It’s implemented as a function which fills in the “holes” in the script described above.
delay 1.5 line exists to give Octopress enough time to do its thing before trying to reload Chrome. Octopress is pretty slow.
In the next chunk, we handle the
In the case of
delete, we use
rm -i to ask the user to confirm the deletion, and if they do, we generate and then call the script itself (
$0) with the deploy action (so as not to duplicate code). The deploy action deploys the generated site (to GitHub Pages), writes out a refresh script for the deployed site, waits an extra few seconds for GitHub Pages to do its thing, and then runs the reload script. Finally,
blog commits and pushes the
source branch of the repository, after cleaning up its temporary files – the reload script, the log from Octopress’ local preview server, and the time reference (which we’ll come to shortly).
We’re managing a symbolic link called
new_post.md here, which is what we’re going to call
vim on. If a filename is specified, we point the link directly at that file. Otherwise, we’re going to call
rake to set up the file. By default,
rake won’t give any indication to our script of what file it made, so we’re going to make a tweak to the
The first changeset handles the case where I don’t want to overwrite the existing post, but I do want to proceed to edit it (and deploy the edits). The last two lines simply point
new_post.md at the right spot so our script can call
vim on it. Before we call vim, though, we have to set up the deploy-on-save feature and the live(ish)-preview feature…
.timeref is an empty file which keeps track of the time slightly before vim was launched. In a “successful” session, the modification time of the post file should be newer than
.timeref, whereas if you
:q! immediately, it won’t be. Now, it’s worth pointing out that the live-preview requires saving along the way, so if you want to abort after previewing, use
vim’s command for exiting with a nonzero status code (so the shell script knows what’s up). The script supports both mechanisms, so that if you are aborting immediately but forget to
:cq, The Right Thing should happen.
Now we’re going to kill off any existing preview processes (they really start to pile up otherwise!) and launch a new one. We also log its
stderr so you can see what the preview process is up to if you want (
tail -f rake_preview.log).
We give the preview process a little time to get started and then display the preview in the browser so the user knows what they’re working from.
This is the last piece of the script, where we actually run
vim and then take the appropriate action after it exits. We’re giving
vim a number of commands on the command line, including setting auto-wrapping at 80 columns (
tw=80), scrolling to the bottom of the file (
+), and changing to the directory the script was run from (set all the way back on line 3). Most importantly, we’re forcing a normal-mode mapping of
C-g to the reload script!
vim exits, we capture its return code with
$?. Then we check if the file has actually been saved. Either it has, or (
||) the status really ought to be nonzero. If the status is still
0, then we do one final preview and shift into deploy mode. Otherwise, we remove the file that
new_post.md points to, remove
new_post.md itself, and reload6.
Putting it all together
All of the files for theming etc. are available here. I’ve spent way too much time tweaking the CSS, and fixing various peeves with the way Octopress renders – I could write an entire other blog post about that, but I probably won’t.↩
:x. My muscle memory has been
:wqfor many years and I haven’t yet made a serious effort to retrain.↩
One example where we don’t want these actions is if the blog post was aborted. Then there’s no sense in tabbing back to the preview just to show that it’s gone, but if the user is looking at the preview anyway, may as well refresh it to reflect the abort.↩
Chrome will even restore your scroll position once the refresh is finished.↩
You can also pass AppleScript on
osascript’s command line using the
-eoption, but only one line of AppleScript at a time. And since there’s no statement separator in AppleScript, we can’t easily transform an arbitrary script into a one-liner (like we could in
bash, or many other more sensible languages).↩
using a newly generated AppleScript which won’t cause Chrome to switch the active tab, in case the abort was related to something else having come up.↩