<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>.autotest</filename>
    </added>
    <added>
      <filename>spec/thor_runner_spec.rb</filename>
    </added>
    <added>
      <filename>task.thor</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -28,7 +28,9 @@ spec = Gem::Specification.new do |s|
   
   s.require_path = 'lib'
   s.autorequire = GEM
-  s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob(&quot;{lib,specs}/**/*&quot;)
+  s.bindir = &quot;bin&quot;
+  s.executables = %w( thor )
+  s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob(&quot;{bin,lib,specs}/**/*&quot;)
 end
 
 Rake::GemPackageTask.new(spec) do |pkg|
@@ -43,6 +45,7 @@ Spec::Rake::SpecTask.new do |t|
   t.spec_opts &lt;&lt; &quot;-fs --color&quot;
 end
 
+task :specs =&gt; :spec
 
 desc &quot;install the gem locally&quot;
 task :install =&gt; [:package] do</diff>
      <filename>Rakefile</filename>
    </modified>
    <modified>
      <diff>@@ -0,0 +1,131 @@
+require &quot;thor&quot;
+
+class Thor::Util
+  # @public
+  def self.constant_to_thor_path(str)
+    snake_case(str).squeeze(&quot;:&quot;)
+  end
+
+  # @public
+  def self.constant_from_thor_path(str)
+    make_constant(to_constant(str))
+  end
+
+  private
+  # @private
+  def self.to_constant(str)
+    str.gsub(/:(.?)/) { &quot;::#{$1.upcase}&quot; }.gsub(/(?:^|_)(.)/) { $1.upcase }
+  end
+  
+  # @private
+  def self.make_constant(str)
+    list = str.split(&quot;::&quot;)
+    obj = Object
+    list.each {|x| obj = obj.const_get(x) }
+    obj
+  end
+  
+  # @private
+  def self.snake_case(str)
+    return str.downcase if str =~ /^[A-Z]+$/
+    str.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&amp;') =~ /_*(.*)/
+      return $+.downcase
+  end  
+  
+end
+
+class Thor::Runner &lt; Thor
+  
+  def self.globs_for(path)
+    [&quot;#{path}/Thorfile&quot;, &quot;#{path}/*.thor&quot;, &quot;#{path}/tasks/*.thor&quot;, &quot;#{path}/lib/tasks/*.thor&quot;]
+  end
+
+  map &quot;-T&quot; =&gt; :list
+  
+  desc &quot;list [SEARCH]&quot;, &quot;list the available thor tasks&quot;
+  method_options :force =&gt; :boolean
+  def list(search = &quot;&quot;)
+    search = /.*#{search}.*/
+    thorfiles.each {|f| load f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
+    
+    # Calculate the largest base class name
+    max_base = Thor.subclasses.max do |x,y| 
+      Thor::Util.constant_to_thor_path(x.name).size &lt;=&gt; Thor::Util.constant_to_thor_path(y.name).size
+    end.name.size
+    
+    # Calculate the size of the largest option description
+    max_left_item = Thor.subclasses.max do |x,y| 
+      (x.help_list &amp;&amp; x.help_list.max.usage + x.help_list.max.opt).to_i &lt;=&gt; 
+      (y.help_list &amp;&amp; y.help_list.max.usage + y.help_list.max.opt).to_i
+    end
+    
+    max_left = max_left_item.help_list.max.usage + max_left_item.help_list.max.opt
+    
+    Thor.subclasses.map {|k| k.help_list}.compact.each do |item|
+      display_tasks(item, max_base, max_left)
+    end
+  end
+  
+  def method_missing(meth, *args)
+    meth = meth.to_s
+    unless meth =~ /:/
+      puts &quot;Thor tasks must contain a :&quot;
+      return
+    end
+    
+    klass = meth.split(&quot;:&quot;)[0...-1].join(&quot;:&quot;)
+    to_call = meth.split(&quot;:&quot;).last
+    begin
+      klass = Thor::Util.constant_from_thor_path(klass)
+    rescue NameError
+      puts &quot;There was no available namespace `#{klass}'.&quot;
+      return
+    end
+    
+    ARGV.replace [to_call, ARGV[1..-1]].compact
+    klass.start
+  end
+  
+  private
+  def display_tasks(item, max_base, max_left)
+    base = Thor::Util.constant_to_thor_path(item.klass.name)
+    item.usages.each do |name, usage|
+      format_string = &quot;%-#{max_left + max_base + 5}s&quot;
+      print format_string %
+        &quot;#{base}:#{item.usages.assoc(name).last} #{display_opts(item.opts.assoc(name) &amp;&amp; item.opts.assoc(name).last)}&quot;
+      puts item.descriptions.assoc(name).last
+    end    
+  end
+  
+  def display_opts(opts)
+    return &quot;&quot; unless opts
+    opts.map do |opt, val|
+      if val == true || val == &quot;BOOLEAN&quot;
+        &quot;[#{opt}]&quot;
+      elsif val == &quot;REQUIRED&quot;
+        opt + &quot;=&quot; + opt.gsub(/\-/, &quot;&quot;).upcase
+      elsif val == &quot;OPTIONAL&quot;
+        &quot;[&quot; + opt + &quot;=&quot; + opt.gsub(/\-/, &quot;&quot;).upcase + &quot;]&quot;
+      end
+    end.join(&quot; &quot;)
+  end  
+  
+  def thorfiles
+    path = Dir.pwd
+    system_thorfiles = Dir[&quot;#{ENV[&quot;HOME&quot;]}/.thor/**/*.thor&quot;]
+    thorfiles = []
+    
+    # Look for Thorfile or *.thor in the current directory or a parent directory, until the root
+    while thorfiles.empty?
+      thorfiles = Dir[*Thor::Runner.globs_for(path)]
+      path = File.dirname(path)
+      break if path == &quot;/&quot;
+    end
+    thorfiles
+  end
+    
+end
+
+unless defined?(Spec)
+  Thor::Runner.start
+end
\ No newline at end of file</diff>
      <filename>bin/thor</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,19 @@
 require &quot;#{File.dirname(__FILE__)}/getopt&quot;
 
 class Thor
+  def self.inherited(klass)
+    subclass_files[File.expand_path(caller[0].split(&quot;:&quot;)[0])] &lt;&lt; klass
+    subclasses &lt;&lt; klass
+  end
+  
+  def self.subclass_files
+    @subclass_files ||= Hash.new {|h,k| h[k] = []}
+  end
+  
+  def self.subclasses
+    @subclasses ||= []
+  end
+  
   def self.method_added(meth)
     return if !public_instance_methods.include?(meth.to_s) || !@usage
     @descriptions ||= []
@@ -26,6 +39,31 @@ class Thor
     end
   end
 
+  def self.help_list
+    return nil unless @usages
+    @help_list ||= begin
+      max_usage = @usages.max {|x,y| x.last.to_s.size &lt;=&gt; y.last.to_s.size}.last.size
+      max_opts  = @opts.empty? ? 0 : format_opts(@opts.max {|x,y| x.last.to_s.size &lt;=&gt; y.last.to_s.size}.last).size 
+      max_desc  = @descriptions.max {|x,y| x.last.to_s.size &lt;=&gt; y.last.to_s.size}.last.size
+      Struct.new(:klass, :usages, :opts, :descriptions, :max).new(
+        self, @usages, @opts, @descriptions, Struct.new(:usage, :opt, :desc).new(max_usage, max_opts, max_desc)
+      )
+    end
+  end
+  
+  def self.format_opts(opts)
+    return &quot;&quot; unless opts
+    opts.map do |opt, val|
+      if val == true || val == &quot;BOOLEAN&quot;
+        &quot;[#{opt}]&quot;
+      elsif val == &quot;REQUIRED&quot;
+        opt + &quot;=&quot; + opt.gsub(/\-/, &quot;&quot;).upcase
+      elsif val == &quot;OPTIONAL&quot;
+        &quot;[&quot; + opt + &quot;=&quot; + opt.gsub(/\-/, &quot;&quot;).upcase + &quot;]&quot;
+      end
+    end.join(&quot; &quot;)
+  end
+  
   def self.start
     meth = ARGV.shift
     params = []
@@ -42,51 +80,22 @@ class Thor
       params &lt;&lt; options
     end
     new(meth, params).instance_variable_get(&quot;@results&quot;)
-  end
-  
-  def thor_usages
-    self.class.instance_variable_get(&quot;@usages&quot;)
-  end
-  
-  def thor_descriptions
-    self.class.instance_variable_get(&quot;@descriptions&quot;)
-  end
-
-  def thor_opts
-    self.class.instance_variable_get(&quot;@opts&quot;)
   end  
-  
 
   def initialize(op, params)
-    @results = send(op.to_sym, *params) if public_methods.include?(op)
+    @results = send(op.to_sym, *params) if public_methods.include?(op) || !methods.include?(op)
   end
-  
-  private
-  def format_opts(opts)
-    return &quot;&quot; unless opts
-    opts.map do |opt, val|
-      if val == true || val == &quot;BOOLEAN&quot;
-        opt
-      elsif val == &quot;REQUIRED&quot;
-        opt + &quot;=&quot; + opt.gsub(/\-/, &quot;&quot;).upcase
-      elsif val == &quot;OPTIONAL&quot;
-        &quot;[&quot; + opt + &quot;=&quot; + opt.gsub(/\-/, &quot;&quot;).upcase + &quot;]&quot;
-      end
-    end.join(&quot; &quot;)
-  end
-  
-  public
+    
   desc &quot;help&quot;, &quot;show this screen&quot;
   def help
+    list = self.class.help_list
     puts &quot;Options&quot;
     puts &quot;-------&quot;
-    max_usage = thor_usages.max {|x,y| x.last.to_s.size &lt;=&gt; y.last.to_s.size}.last.size
-    max_opts  = thor_opts.empty? ? 0 : format_opts(thor_opts.max {|x,y| x.last.to_s.size &lt;=&gt; y.last.to_s.size}.last).size 
-    max_desc  = thor_descriptions.max {|x,y| x.last.to_s.size &lt;=&gt; y.last.to_s.size}.last.size
-    thor_usages.each do |meth, usage|
-      format = &quot;%-&quot; + (max_usage + max_opts + 4).to_s + &quot;s&quot;
-      print format % (thor_usages.assoc(meth)[1] + (thor_opts.assoc(meth) ? &quot; &quot; + format_opts(thor_opts.assoc(meth)[1]) : &quot;&quot;))
-      puts  thor_descriptions.assoc(meth)[1]
+    list.usages.each do |meth, usage|
+      format = &quot;%-&quot; + (list.max.usage + list.max.opt + 4).to_s + &quot;s&quot;
+      print format % (list.usages.assoc(meth)[1] + (list.opts.assoc(meth) ? &quot; &quot; + self.class.format_opts(list.opts.assoc(meth)[1]) : &quot;&quot;))
+      puts  list.descriptions.assoc(meth)[1]
     end
-  end  
+  end
+  
 end
\ No newline at end of file</diff>
      <filename>lib/thor.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,2 +1,31 @@
 $TESTING=true
 $:.push File.join(File.dirname(__FILE__), '..', 'lib')
+
+module Spec::Expectations::ObjectExpectations
+  alias_method :must, :should
+  alias_method :must_not, :should_not
+end
+
+class StdOutCapturer
+  attr_reader :output
+
+  def initialize
+    @output = &quot;&quot;
+  end
+
+  def self.call_func
+    begin
+      old_out = $stdout
+      output = new
+      $stdout = output
+      yield
+    ensure
+      $stdout = old_out
+    end
+    output.output
+  end
+
+  def write(s)
+    @output += s
+  end
+end</diff>
      <filename>spec/spec_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,27 +1,6 @@
 require File.dirname(__FILE__) + '/spec_helper'
 require &quot;thor&quot;
 
-class StdOutCapturer
-  attr_reader :output
-
-  def initialize
-    @output = &quot;&quot;
-  end
-
-  def self.call_func
-    old_out = $stdout
-    output = new
-    $stdout = output
-    yield
-    $stdout = old_out
-    output.output
-  end
-
-  def write(s)
-    @output += s
-  end
-end
-
 class MyApp &lt; Thor
   
   map &quot;-T&quot; =&gt; :animal
@@ -53,76 +32,95 @@ class MyApp &lt; Thor
   def baz(bat, opts)
     [bat, opts]
   end
+  
+  def method_missing(meth, *args)
+    [meth, args]
+  end
+  
+  private
+  desc &quot;what&quot;, &quot;what&quot;
+  def what
+  end
 end
 
 describe &quot;thor&quot; do
   it &quot;calls a no-param method when no params are passed&quot; do
     ARGV.replace [&quot;zoo&quot;]
-    MyApp.start.should == true
+    MyApp.start.must == true
   end
   
   it &quot;calls a single-param method when a single param is passed&quot; do
     ARGV.replace [&quot;animal&quot;, &quot;fish&quot;]
-    MyApp.start.should == [&quot;fish&quot;]
+    MyApp.start.must == [&quot;fish&quot;]
   end
   
   it &quot;calls the alias of a method if one is provided via .map&quot; do
     ARGV.replace [&quot;-T&quot;, &quot;fish&quot;]
-    MyApp.start.should == [&quot;fish&quot;]
+    MyApp.start.must == [&quot;fish&quot;]
   end
   
   it &quot;raises an error if a required param is not provided&quot; do
     ARGV.replace [&quot;animal&quot;]
-    lambda { MyApp.start }.should raise_error(ArgumentError)
+    lambda { MyApp.start }.must raise_error(ArgumentError)
   end
   
   it &quot;calls a method with an optional boolean param when the param is passed&quot; do
     ARGV.replace [&quot;foo&quot;, &quot;one&quot;, &quot;--force&quot;]
-    MyApp.start.should == [&quot;one&quot;, {&quot;force&quot; =&gt; true, &quot;f&quot; =&gt; true}]
+    MyApp.start.must == [&quot;one&quot;, {&quot;force&quot; =&gt; true, &quot;f&quot; =&gt; true}]
   end
   
   it &quot;calls a method with an optional boolean param when the param is not passed&quot; do
     ARGV.replace [&quot;foo&quot;, &quot;one&quot;]
-    MyApp.start.should == [&quot;one&quot;, {}]
+    MyApp.start.must == [&quot;one&quot;, {}]
   end
   
   it &quot;calls a method with a required key/value param&quot; do
     ARGV.replace [&quot;bar&quot;, &quot;one&quot;, &quot;two&quot;, &quot;--option1&quot;, &quot;hello&quot;]
-    MyApp.start.should == [&quot;one&quot;, &quot;two&quot;, {&quot;option1&quot; =&gt; &quot;hello&quot;, &quot;o&quot; =&gt; &quot;hello&quot;}]
+    MyApp.start.must == [&quot;one&quot;, &quot;two&quot;, {&quot;option1&quot; =&gt; &quot;hello&quot;, &quot;o&quot; =&gt; &quot;hello&quot;}]
   end
   
   it &quot;errors out when a required key/value option is not passed&quot; do
     ARGV.replace [&quot;bar&quot;, &quot;one&quot;, &quot;two&quot;]
-    lambda { MyApp.start }.should raise_error(Getopt::Long::Error)
+    lambda { MyApp.start }.must raise_error(Getopt::Long::Error)
   end
   
   it &quot;calls a method with an optional key/value param&quot; do
     ARGV.replace [&quot;baz&quot;, &quot;one&quot;, &quot;--option1&quot;, &quot;hello&quot;]
-    MyApp.start.should == [&quot;one&quot;, {&quot;option1&quot; =&gt; &quot;hello&quot;, &quot;o&quot; =&gt; &quot;hello&quot;}]
+    MyApp.start.must == [&quot;one&quot;, {&quot;option1&quot; =&gt; &quot;hello&quot;, &quot;o&quot; =&gt; &quot;hello&quot;}]
   end
   
   it &quot;calls a method with an empty Hash for options if an optional key/value param is not provided&quot; do
     ARGV.replace [&quot;baz&quot;, &quot;one&quot;]
-    MyApp.start.should == [&quot;one&quot;, {}]
+    MyApp.start.must == [&quot;one&quot;, {}]
+  end
+  
+  it &quot;calls method_missing if an unknown method is passed in&quot; do
+    ARGV.replace [&quot;unk&quot;, &quot;hello&quot;]
+    MyApp.start.must == [:unk, [&quot;hello&quot;]]
+  end
+  
+  it &quot;does not call a private method no matter what&quot; do
+    ARGV.replace [&quot;what&quot;]
+    MyApp.start.must == nil
   end
   
   it &quot;provides useful help info for a simple method&quot; do
-    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.should =~ /zoo +zoo around/
+    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.must =~ /zoo +zoo around/
   end
   
   it &quot;provides useful help info for a method with one param&quot; do
-    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.should =~ /animal TYPE +horse around/
+    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.must =~ /animal TYPE +horse around/
   end  
   
   it &quot;provides useful help info for a method with boolean options&quot; do
-    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.should =~ /foo BAR \-\-force +do some fooing/
+    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.must =~ /foo BAR \[\-\-force\] +do some fooing/
   end
   
   it &quot;provides useful help info for a method with required options&quot; do
-    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.should =~ /bar BAZ BAT \-\-option1=OPTION1 +do some barring/
+    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.must =~ /bar BAZ BAT \-\-option1=OPTION1 +do some barring/
   end
   
   it &quot;provides useful help info for a method with optional options&quot; do
-    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.should =~ /baz BAT \[\-\-option1=OPTION1\] +do some bazzing/
+    StdOutCapturer.call_func { ARGV.replace [&quot;help&quot;]; MyApp.start }.must =~ /baz BAT \[\-\-option1=OPTION1\] +do some bazzing/
   end    
 end
\ No newline at end of file</diff>
      <filename>spec/thor_spec.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>82ff27a67d7fa1078c231bb52b71cbd9b3e0fb37</id>
    </parent>
  </parents>
  <author>
    <name>Yehuda Katz</name>
    <email>wycats@gmail.com</email>
  </author>
  <url>http://github.com/wycats/thor/commit/b522f05ef166d142e80e30d264ae0dfba09ac4f8</url>
  <id>b522f05ef166d142e80e30d264ae0dfba09ac4f8</id>
  <committed-date>2008-05-07T19:28:50-07:00</committed-date>
  <authored-date>2008-05-07T19:28:50-07:00</authored-date>
  <message>First pass of Thor::Runner</message>
  <tree>544c3e15034bccf357738955403bb4abed570e99</tree>
  <committer>
    <name>Yehuda Katz</name>
    <email>wycats@gmail.com</email>
  </committer>
</commit>
