Yehuda Katz is a member of the Ruby on Rails core team, and lead developer of the Merb project. He is a member of the jQuery Core Team, and a core contributor to DataMapper. He contributes to many open source projects, like Rubinius and Johnson, and works on some he created himself, like Thor.

@gmcintire Passenger may be setting GEM_HOME differently -- can you print out ENV["GEM_HOME"] in passenger?

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 &lt; 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!

Share and Enjoy:
  • Digg
  • Reddit
  • HackerNews
  • Twitter

8 Responses to “Some Thor News”

I’ll try to give an edge copy of Thor a run on my Windows machine at work to see how it’s doing. In the past I’ve really wrestled with getting Thor to behave with Windows.

@james if it doesn’t work, please give me full details so I can get it fixed for 1.0!

Hi Katz, are you fine? Does Thor support Modules? I mean:

module Foo
class Bar < Thor
def baz
end
end
end

thor foo:bar:baz

If yes, would be nice to have thor as default task runner on Rails3 instead of Rake and all those ugly script/* things that are used today because Rake doesn’t deal with parameters very well (and thor does!)

Yep! That should work :)

Great! Now you just have to convince the rest of the Rails Core Team to switch to thor and abandon script/*! :D

Congratulations to your job on both Rails and Merb, also by your blog and to keep us up to date with the Rails 3 work!

How about dependencies and testing?

Can we define dependencies like what we do with Rake?

I understand Thor is just plain old Ruby classes but there may be some special tricks for mocking or options or dependencies. Can you please share some example of how you test thor with test/unit and rspec?

I have created a few Rails 3 generators. I can install these as gems and use $ gem bundle to install them in a Rails 3 app in order to have them enabled with script/generate.
But I assume there is also a way to install these generators globally on my system so I don’t have to go through these steps for every Rails app I create?

Leave a Reply