Last December I went down to Philadelphia for WordCamp US 2016. Met some great people, heard some great talks, overall had a good time. Holding the after party at the Academy of Natural Sciences was a genius move.

Sitting at Lisa Yoder’s talk on Version Control Your Life: Alternate Uses For Git inspired me to try taking notes in Markdown (versioned in git) instead of Evernote. I’m trying to move away from Evernote anyway and it made perfect sense. I’m always working on the command line; I always have Typora open as scratch-space.

I ran into an immediate (and silly) snag. All the WCUS sessions are titled “This Is The Name Of My Awesome Talk”. That’s a bad filename if you’re working on the command line. Ideally I want my notes on that talk to be called “this-is-the-name-of-my-awesome-talk.md”. Manually typing all that is boring, and I’m lazy. Better way? Better way.

Hammering markdown

Separately, I’d been playing around with Hammerspoon the last few days. Hammerpsoon is an automation engine for OS X. You can write Lua modules to perform various tasks, triggered by system events or manual invocation. It seemed cool and all, but I hadn’t found a concrete use case. Sitting in that talk, I had an idea—use Hammerspoon to convert arbitrary text (like a talk title) to an URL slug, which I could then use as a filename.

The actual module is pretty short; 38 lines including comments. Aside from some Wikipedia modules I don’t really have exposure to Lua, and Hammerspoon itself was terra incognita. Let’s step through this:

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "T", function()

hs is the primary Hammerspoon object. I’m binding ⌃⌥⌘T; pressing that combination will activate the code within the module.

    local current = hs.application.frontmostApplication()

Brings the application forward so that we can send events to it.

    local chooser = hs.chooser.new(function(chosen)
        current:activate()
        hs.pasteboard.setContents(chosen.text)
    end)

Creates a chooser window and tells it to send the result of that window to the system clipboard. Now, here’s the actual callback:

    chooser:queryChangedCallback(function(string)
        local choices = {
            {
                ["text"] = string:lower(string),
                ["subText"] = "All lower case"
            },
            {
                ["text"] = string:upper(string),
                ["subText"] = "All upper case"
            },
            {
                ["text"] = string.gsub(" "..string, "%W%l", string.upper):sub(2),
                ["subText"] = "Capitalized case"
            },
            {
                ["text"] = string.gsub(string.gsub(string:lower(string),"[^ A-Za-z0-9]",""),"[ ]+","-"),
                ["subText"] = "Post slug"
            }
        }
        chooser:choices(choices)
    end)

This is where we actually create the chooser window and populate it with options. This is mostly string math. Hat tip to n1xx1 on stackoverflow for the capitalized case logic and Draco Blue for the post slug.

We’re almost done!

    chooser:searchSubText(false)

Tell the chooser to not search the sub text. Frankly I’m not sure what it does, but I saw it done in another module.

    chooser:show()

Finally, display the chooser window.

Putting it all together

With the module installed, I’m a keyboard combination and a copy-paste sequence away from a post-friendly slug for any bit of arbitrary text:

I finished up the module during the second round of talks. At the time I was half-worried that the project violated the XKCD rule, but I’ve found myself using it more and more over the last few months. Beyond note-taking, it works well for storing electronic articles and books. Besides, I had a good time doing it and learned something new.