I just signed an employment agreement with Microsoft. Who would have ever imagined that happening?

Trollop news

Looks like there was a Ruby Inside article featuring Trollop a few weeks ago. Partially as a result of this, I have at least two other people contributing patches. For a project that's been around for a few years and basically had no one but me use it, that's a nice change of pace.

I've also moved it over from SVN to git (hosted on Gitorious), which probably will help some.

Google textfile auto-titleing

If you search for "ditz readme" on the Googles, the correct result, which is a text file and not an HTML page, appears with the title "DitzのREADME". This is probably because there's a link to it titled as such in this Japanese description of a Ditz emacs mode. Apparently Google prefers that title over the link just called "README" on the the Ditz main page.

Ditz 0.4, and the magic of Ruby DSLs

I've just released Ditz 0.4. The big-ticket item in this release is the plugin system, which makes it very easy to tweak Ditz's models, views and controllers. There's an included git plugin which does some nice things like linking git commits and git branches to individual Ditz issues.

The new bash completion is pretty nice too. The completion code has been reworked a bit and now ties in very nicely with the argument processing. Check out this code from the Ditz's controller (operator.rb, for those following along from your repo):

operation :start, "Start work on an issue", :unstarted_issue
def start project, config, issue
## ...

Just by calling the operation method, we get:
  1. Argument checking. There must be one argument to 'ditz start', and it must be an unstarted issue. Any violations are handled nicely for us without having to invoke the method.
  2. Help messages in 'ditz help' and 'ditz help start'.
  3. Argument completion. Running 'ditz start <options>' outputs a list of possible completions for a command (in this case, all unstarted issues) and then exits. Shell completion scripts can parse this output and present it to you when you hit tab.

So that's a little DSL that I think turned out well.

Writing a plugin is also nicely DSLified. Here are some examples from plugin/git.rb:

class Issue
field :git_branch, :ask => false

def git_commits
## ...

Here we reopen the Issue class and add a field called git_branch, and we specify that the UI shouldn't ask for this field when an issue is created, since I decided that would be too annoying. (We'll see how we allow the user to explicitly set it below.) We also add a method that's responsible for actually getting the commits out of git.

Since our configuration file is just a Ditz model object, we can do the same thing to add the configuration parameters we need:

class Config
field :git_commit_url_prefix,
:prompt => "URL prefix (if any) to link git commits to"
field :git_branch_url_prefix,
:prompt => "URL prefix (if any) to link git branches to"

We'll use those two fields to add some links when we generate HTML.

Adding commands to Ditz's controller is just as easy. Just reopen the class:

class Operator
operation :set_branch, "Set the git feature branch of an issue", :issue, :maybe_string
def set_branch project, config, issue, maybe_string
## ...

So now we have a set-branch command that takes an issue name, and an optional branch name. And it's a first-class citizen alongside every other command: shows up in the help page, has argument auto-completion, etc.

Finally, let's see how we modify the views. One thing we'd like to see is the git branch for an issue, if it's been set.

class ScreenView
add_to_view :issue_summary do |issue, config|
" Git branch: #{issue.git_branch || 'none'}\n"

Here we've opened up the ScreenView class (which is used for generating the screen output, as opposed to the HTML output) and added a closure to the summary section, which prints out the value of the model field we added above. The HTML version is similar:

class HtmlView
add_to_view :issue_summary do |issue, config|
next unless issue.git_branch
[{ :issue => issue,
:url_prefix => config.git_branch_url_prefix }, <<EOS]
<td class='attrname'>Git branch:</td>
<td class='attrval'>
<%= url_prefix ?
link_to([url_prefix, issue.git_branch].join,
issue.git_branch) :
h(issue.git_branch) %>

The HTML generation returns ERB, and a hash of variables necessary for resolving it. (In this case we could also have used string substitution, but that's not always the case—you might want to make use of variables that are only available at generation time.) Note that it also makes use of some of the convenient helper functions (like link_to and h), which I've helpfully defined for you in html.rb.

So that's how to modify ditz's models, views and controllers in one easy go. You can add fields and helper methods to model objects (including the configuration object), you can add commands to the controller, and you can add view elements to the screen and HTML output.

For reference, the complete source code to the git plugin is here.

Vim ruby syntax comment reformatting

The vim ruby syntax seems to screw up comments that have multiple hashes. E.g. I like to differentiate

### section heading comments,
## non-inline comments, and
x = a + b # inline comments

But reformatting the comments (e.g. with "gq}") always screws them up, unless you do:

$ mkdir -p ~/.vim/after/syntax
$ cat > ~/.vim/after/syntax/ruby.vim
set comments=n:#

which tells vim that multiple hash marks are ok.

Rethinking Sup part II

In Rethinking Sup part I, I concluded that Sup the MUA is an evolutionary dead end, and that the future lies in Sup the Service (STS). But what does that mean?

One thing I want to make clear it does not mean is any abandonment of the Sup curses UI. That particular "user experience" has been refined over the past few years to become my ideal email interface. It would be silly to throw that away.

What will happen to the curses code is that it will become one client among (hopefully) many. Once there's a clear delineation between UI and backend, you can make a UI choice independent of making a choice to use Sup in the first place. You can run sup-curses-client if you want. Or you can build a web interface, or an Openmoko interface. Working with ncurses has always been the least enjoyable part of Sup, so maybe I'll actually enjoy learning Javascript.

What backend functionality will STS actually provide? If I were simply reworking Sup into a client and a server, the obvious answer would be "a searchable, labelable, threaded view of large amounts of email".

But reworking Sup is a great time to extend its original goals. In particular, I would love for STS to handle to other types of documents besides email. I've always used my inbox as a mechanism for writing notes to myself. I've experimented briefly with reading RSS feeds through it. I'd like STS to support email, of course, but not to be limited by it.

My grand vision: STS will be a searchable, labelable, threaded view of large numbers of documents.

You can throw whatever you want in there, and STS will store it, thread it, and let you label and search for it. Email, RSS feeds, notes, jabber and IRC logs, web pages, RI documents—I want you to be able to throw them all in there. I want you to be able to annotate any of those things by adding notes and threading them against the original objects. Basically I want STS to be the primary tool you use for organizing and recalling all the textual information you've ever encountered in your life.

Cool, huh?

There's another convenient benefit to this transformation: no one will expect STS to act like a MUA. STS does its own storage. You add your email and your other documents to the server and then you can throw those files away (or not). There are no more questions of supporting IMAP or various mbox dialects or "why doesn't Sup treat Maildir correctly". The files are in STS, and once they're their, they're out of your hands. You'll be able to export them, of course, and if you're crazy you might be able to write an IMAP server translation layer for STS, but there will be no more expectation of realtime Maildir handling. As I explained in part I, that's a game I don't want to play.

STS is a grander vision than a MUA, and it no longer has to be hobbled by the constraints of being expected to act like one.

Some other nice benefits of reworking Sup into SYS:
  • You'll be able to run multiple clients at once.
  • It's an opportunity to rework some things. For example, one of the most noticeably slow operations in Sup ("Classic") is assembling a large thread. This is because I made a decision early on to do all threading at search time. That made certain things easier (in particular, I could change the threading model without having to rescan the entire index), but in retrospect the cost is too high. STS will maintain document trees directly.
  • I can replace Ferret with Sphinx. It's been a good couple years, but the periodic non-deterministic index corruption that's been an issue for over a year is an exit sign to me. Working with Sphinx is nowhere nearly as nice as working with Ferret, but speed and stability go a long way.
I've been working on the code for STS on and off for the past couple weeks and it's slowly starting to come together. Once the major components have at least been all sketched, I will host a git repo.

Dr. Horrible

Really, really digging Dr. Horrible. I guess this post means I am coming out of the closet as a big Joss Whedon fan.

Stops being free tomorrow, so see it now.

ruby readline filename tab completion

Here's how to do it right:

require 'readline'
def ask_for_filename question, start_dir=""
Readline.completion_append_character = nil
Readline.completion_proc = lambda do |prefix|
files = Dir["#{start_dir}#{prefix}*"] { |f| File.expand_path(f) }.
map { |f| ? f + "/" : f }
Readline.readline question

Blog Archive