Some Thor News

Over the past few months, people have been using thor for increasingly serious things. The project itself was extracted out of my textmate gem, and is a nice Railsish DSL for command-line applications. It uses classes and methods as its abstraction, in much the same way that Rails uses classes and methods. Here’s an example:

class Speak < Thor
  desc "name", "the name to say hello to"
  def hello(name)
    puts "Hello #{name}"
  end
end

Put a class like that in a file named Thorfile or *.thor into any directory or its tasks directory, and you’ll be able to invoke thor speak:hello Yehuda, and it will print Hello Yehuda. Thor also supports full option parsing:

class Speak < Thor
  desc "name", "the name to say hello to"
  method_options :loudly => false
  def hello(name)
    name.upcase! if options[:loudly]
    puts "Hello #{name}"
  end
end

Interestingly, the thor runner itself is just a thor script. That’s because the following is a valid stand-alone Ruby file that does not require the thor runner:

require 'rubygems'
require 'thor'

class Speak < Thor

  desc "name", "the name to say hello to"
  def hello(name)
    puts "Hello #{name}"
  end
end

Speak.start

You would invoke that script with binary_name hello Yehuda. The thor runner simply uses method_missing on the Thor::Runner class, so that thor foo:bar works, even though there is no foo:bar method in Thor::Runner. Here’s the method_missing method on Thor::Runner:

def method_missing(meth, *args)
  meth = meth.to_s
  super(meth.to_sym, *args) unless meth.include? ?:

  initialize_thorfiles(meth)
  task = Thor \[meth\]
  task.parse task.klass.new, ARGV[1..-1]
end

In the past few weeks, I finally got around to adding some sorely needed features. The first, to transparently wrap thor files in a Thor::Tasks namespace, so, for instance, the Merb < Thor class doesn’t conflict with the Merb module. The second was to add thor bundles (a surprisingly small commit), which allow the packaging of several files into a bundle. In the case of thor bundles, all you need to do is name a directory *.thor (instead of a file) and put a main.thor file inside. Everything will then work as expected.

Finally, thor tasks can be installed systemwide by simply calling thor install filename or thor install http://example.com/foo.thor. All of thor’s features, including bundling, work transparently with installed tasks. As far as thor is concerned, installed gems are available in addition to any local gems, everywhere.

The final bit of news is that SproutCore (which is used in Apple’s MobileMe and iWork.com), which is already built on Merb, will be adopting thor for its build tools. Charles Jolley (of SproutCore) recently submitted quite a few interesting patches yesterday to make thor ready for use with SproutCore, and I expect to announce a 1.0 release in the next few weeks. I plan to fix up global options, make it possible to install bundles from remote locations. Is there something you want in thor before the 1.0 release? Let me know!