<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>lib/go/installer.rb</filename>
    </added>
    <added>
      <filename>lib/go/recursive_http_fetcher.rb</filename>
    </added>
    <added>
      <filename>test/fixtures/plugins/hello.rb</filename>
    </added>
    <added>
      <filename>test/fixtures/plugins/palace/party_palace.rb</filename>
    </added>
    <added>
      <filename>test/fixtures/plugins/panda.rb</filename>
    </added>
    <added>
      <filename>test/fixtures/plugins/panda_party.rb</filename>
    </added>
    <added>
      <filename>test/fixtures/plugins/todo.rb</filename>
    </added>
    <added>
      <filename>test/fixtures/reserved/commands.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,6 +1,5 @@
 #!/usr/bin/ruby
 
-path = File.join(File.dirname(__FILE__), '..', 'lib', 'go')
-require path
+require(File.join(File.dirname(__FILE__), '..', 'lib', 'go'))
 
 Go.application.run</diff>
      <filename>bin/go_dev</filename>
    </modified>
    <modified>
      <diff>@@ -2,22 +2,25 @@ $:.unshift(File.dirname(__FILE__) + &quot;/go/&quot;)
 
 # System gems
 require 'rubygems'
-require 'open-uri'
 require 'cooloptions'
 
 # Go Core classes
 require 'application'
 require 'plugin'
+require 'installer'
 require 'extensions'
+require 'recursive_http_fetcher'
 
 # Ruby libs
+require 'open-uri'
+require 'uri'
 
 module Go
   VERSION = '0.1'
 
   # Current Rake Application
   def self.application
-    @application ||= Go::Application.new
+    @application ||= Go::Application.new(ARGV)
   end
 
   # Set the current Rake application object.</diff>
      <filename>lib/go.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,32 +1,55 @@
 module Go
 
   class Application
+#    class &lt;&lt; self
+#    end
+    attr_reader :args, :plugin_commands, :sorted_plugins, :options
+    attr_accessor :plugin_path
     
+    ReservedCommands = %w(commands edit generate help list setup install plugin)
+
     # Initializes a new Go::Application object - should always be called via a Singleton, e.g.
     #   Go.application.run
-    def initialize
+    def initialize(args = ARGV)
+      @args = args
+      @options = OpenStruct.new
     end
     
     # Main Go Application entry point.
     # * Parses command-line options and calls methods accordingly
     def run
-      @options = CoolOptions.parse!(&quot;[options] (commands|edit|generate|help|setup)&quot;) do |o|
+      @options = CoolOptions.parse!(&quot;[options] (commands|edit|generate|help|list|setup)&quot;) do |o|
         o.desc &quot;Go - the Extensible command-line tool for ninjas, pirates &amp; pandas&quot;
-        o.on &quot;(v)erbose&quot;,              &quot;Turn on verbose mode&quot;,                                       false
+        o.on &quot;(v)erbose&quot;,              &quot;Turn on verbose mode&quot;,        false
+        o.on &quot;(f)orce&quot;,                &quot;Force (the action)&quot;,          false
         o.after do |r|
           r.config_path = self.default_config_path
           if !File.exist?(r.config_path)
             setup
             exit
           end
-          r.params = ARGV.slice(1, ARGV.size)
-          r.action = ARGV.shift || 'commands'
+          r.full_command = @args.join(' ')
+          r.params = @args.slice(1, @args.size)
+          r.action = @args.empty? ? 'commands' : @args[0]
         end
       end
 
       case @options.action
       when 'commands', 'list'
         list_commands
+      when 'install'
+        install_plugin
+      when 'plugin'
+        case @options.params.first
+        when 'list'
+          list_commands
+        when 'install'
+          @option.params.shift
+          install_plugin
+        else
+          puts &quot;Invalid command.  Call go without any params for a list of valid commands.&quot;
+          list_commands
+        end
       when 'generate'
         generate
       when 'setup'
@@ -41,23 +64,46 @@ module Go
 
     end
     
+    def install_plugin
+      if @options.params.empty?
+        raise &quot;Please specify a URL from which to install the plugin, e.g.:  go install http://gist.github.com/14933&quot;
+      end
+      installer = Installer.new(@options.params.first, self)
+      installer.install
+    end
+    
     def run_plugin_command
       init_plugins
-      if @plugin_commands[@options.action]
-        klass = @plugin_commands[@options.action]
+      plugin_pair = nil
+      #  They may have passed &quot;panda party palace&quot;, &quot;panda party&quot;, or &quot;panda&quot;
+      #  We want to give the plugin with the most words -&gt; highest priority
+      @args.size.downto(1) do |i|
+        cmd = @args.slice(0, i).join(' ')
+        plugin_pair = @sorted_plugins.detect {|pair| pair.first == cmd}
+        break if plugin_pair
+      end
+      if plugin_pair
+        klass = plugin_pair[1]
         plugin = klass.new(@options)
         plugin.run
       else
-        puts &quot;No command found for action: #{@options.action}&quot;
+        puts &quot;No command found for action: #{@options.action}&quot; unless test_env?
       end
+      #puts &quot;\t Returning: #{plugin_pair.inspect}&quot;
+      plugin_pair
     end
     
     def list_commands
+      justify = 40
       init_plugins
-      puts &quot;\n Go command list: &quot;
-      @plugin_commands.each do |command, klass|
-        usage = klass.usage_raw ? &quot; # go #{klass.usage_raw}&quot; : ''
-        puts &quot;   #{command}\t  #{usage}&quot;
+      puts &quot;\n Builtin Go commands\n\n&quot;
+      show_command('install', &quot;install &lt;plugin_url&gt;&quot;)
+      show_command('list', &quot;list&quot;)
+      puts &quot;\n Plugin commands\n &quot;
+      @sorted_plugins.each do |pair|
+        command, klass = *pair
+        usage = klass.usage_raw ? klass.usage_raw : command
+        show_command(command, usage)
       end
       puts &quot;\n&quot;
     end
@@ -76,22 +122,7 @@ module Go
       
       install_default_plugins
       
-      puts '
-Default config file has been created in:
-
-  ~/.goconfig
-
-This will be the primary entry-point for editing any config options required by Go plugins.
-
-Default plugins have been placed in the go plugin directory:
-
- ~/.go
- 
-To see a list of available go commands, run go without any arguments:
-
-  go
-
-'
+      puts setup_txt
     end
     
     # Edit the config file (using the system editor)
@@ -105,8 +136,24 @@ To see a list of available go commands, run go without any arguments:
       puts help_txt
     end
     
+    def init_plugins!
+      init_plugins
+    end
+    
+    def clear_plugins!
+      @plugin_commands, @sorted_plugins = nil, nil
+    end
+    
+    def go_dir
+      @plugin_path || File.join(ENV['HOME'], '.go')
+    end
+  
     protected
     
+    def show_command(command_name, usage, justify = 40)
+      puts &quot;   #{command_name.ljust(justify)}\t# go #{usage}&quot;
+    end
+    
     def init_plugins
       contains = Dir.new(go_dir).entries
       contains.delete(&quot;.&quot;); contains.delete(&quot;..&quot;)
@@ -115,18 +162,15 @@ To see a list of available go commands, run go without any arguments:
         if File.directory?(path)
           # Require all .rb files in this dir
           subfiles = Dir.new(path).entries
-          #puts &quot;subfiles = #{subfiles.inspect}&quot;
           subfiles.delete(&quot;.&quot;); subfiles.delete(&quot;..&quot;)
           subfiles.each do |sub|
             sub_path = File.join(path, sub)
-            #puts &quot;\tsub_path = #{sub_path}&quot;
             if !File.directory?(sub_path) &amp;&amp; File.extname(sub_path) == '.rb'
               require sub_path
             end
           end
         elsif File.extname(path) == '.rb'
           # Require it if it's a ruby file
-          #puts &quot;Requiring: #{path}&quot;
           require path
         end
       end
@@ -140,12 +184,44 @@ To see a list of available go commands, run go without any arguments:
       plugins.each do |p|
         command = p.command.to_s
         puts &quot; Plugin: #{p} :: #{command}&quot; if @options.verbose
-        @plugin_commands[command] = p
+        if ReservedCommands.include?(command)
+          puts &quot;WARNING: #{p} attempted to init plugin with reserved command '#{command}'  (plugin *not* loaded)&quot; unless test_env?
+        else
+          @plugin_commands[command] = p
+        end
       end
+      @sorted_plugins = sort_plugins(@plugin_commands)
     end
     
-    def go_dir
-      File.join(ENV['HOME'], '.go')
+    def sort_plugins(plugin_commands)
+      # Now sort 'em - alphabetically first, thgen with higher priority (more words in the command string)
+      #  plugins coming next.
+      sorted_plugins = []
+      plugin_commands.each do |command, klass|
+        sorted_plugins &lt;&lt; [command, klass]
+      end
+      sorted_plugins.sort! do |x,y|
+        cmd_x, cmd_y = x.first, y.first
+        cmd_x_size = cmd_x.split(' ').size
+        cmd_y_size = cmd_y.split(' ').size
+        if (cmd_x_size == cmd_y_size) &amp;&amp; cmd_x_size == 1
+          # Single word command (both), simply sort alphabetically
+          (cmd_x &lt;=&gt; cmd_y)
+        else
+          # One of the commands has multiple words, install the mutli-word command ahead of the other one
+          #   if there is a sub-match
+          sub_cmd_x = cmd_x.split(' ')[0]
+          sub_cmd_y = cmd_y.split(' ')[0]
+          if sub_cmd_x == sub_cmd_y 
+            # e.g. 'panda' and 'panda party'
+            cmd_y_size &lt;=&gt; cmd_x_size
+          else
+            # Otherwise sort alphabetically
+            cmd_x &lt;=&gt; cmd_y
+          end
+        end
+      end
+      sorted_plugins
     end
     
     def default_config_path
@@ -159,7 +235,7 @@ To see a list of available go commands, run go without any arguments:
     end
     
     def help_txt
-      '
+      txt = &lt;&lt;EOF
 To setup the initial config file, execute: 
 
   go setup
@@ -168,13 +244,38 @@ Edit go config using your system edtior:
 
   go edit
 
-'
+EOF
+    end
+    
+    def setup_txt
+      txt = &lt;&lt;EOF
+
+    Default config file has been created in:
+
+      ~/.goconfig
+
+    This will be the primary entry-point for editing any config options required by Go plugins.
+
+    Default plugins have been placed in the go plugin directory:
+
+     ~/.go
+
+    To see a list of available go commands, run go without any arguments:
+
+      go
+
+EOF
     end
     
     def sample_config
       &quot;name: Change Me&quot;
     end
     
+    # Suppress noise when testing
+    def test_env?
+      defined?(Test::Unit::TestCase)
+    end
+    
   end
 
 end</diff>
      <filename>lib/go/application.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,6 +5,17 @@ class Time
   end
 end
 
+class String
+  def to_underscore #(klass)
+    self.dup.gsub(/::/, '/').
+      gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
+      gsub(/([a-z\d])([A-Z])/,'\1_\2').
+      tr(&quot;-&quot;, &quot;_&quot;).
+      downcase
+  end
+end
+
+
 class Module
   def subclasses
     classes = []</diff>
      <filename>lib/go/extensions.rb</filename>
    </modified>
    <modified>
      <diff>@@ -0,0 +1,48 @@
+require(File.join(File.dirname(__FILE__), '..', 'lib', 'go'))
+require 'test/unit'
+require 'set'
+
+class TestGo &lt; Test::Unit::TestCase
+  
+  def test_fixture_plugins_loaded_as_subclasses
+    @app = Go::Application.new(['hello'])
+    @app.plugin_path = File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+    @app.instance_eval('init_plugins')
+    assert_equal [Hello, Panda, PandaParty, PartyPalace, Todo].to_set, Go::Plugin.subclasses.to_set
+  end
+  
+  def test_plugin_with_reserved_command_should_not_get_included_in_command_tree
+    @app = Go::Application.new([])
+    @app.plugin_path = File.join(File.dirname(__FILE__), 'fixtures', 'reserved')
+    @app.instance_eval('init_plugins')
+    assert !@app.plugin_commands.keys.include?('commands')
+  end
+  
+  def test_command_with_most_required_params_has_highest_priority
+    @app = Go::Application.new(['panda party palace'])
+    @app.plugin_path = File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+    @app.instance_eval('init_plugins')
+    assert_equal [&quot;panda party palace&quot;, PartyPalace], @app.run
+  end
+
+  def test_command_with_next_most_params_has_next_highest_priority
+    @app = Go::Application.new(['panda party'])
+    @app.plugin_path = File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+    assert_equal [&quot;panda party&quot;, PandaParty], @app.run
+  end
+  
+  def pending_test_single_word_command_with_params
+    @app = Go::Application.new(['panda', 'this app makes me feel sexy'])
+    @app.plugin_path = File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+    @app.init_plugins!
+    assert_equal [&quot;panda&quot;, Panda], @app.run
+  end
+  
+  ## Installer tests
+  def test_installer_parse_pastie_txt
+    app = Go::Application.new([])
+    app.plugin_path = File.join(File.dirname(__FILE__), 'fixtures', 'reserved')
+    inst = Go::Installer.new('http://pastie.org/285599.txt', app)
+    assert_equal 'http://pastie.org/285599.txt', inst.instance_eval('@url') #inst.send(:url)
+  end
+end
\ No newline at end of file</diff>
      <filename>test/test_go.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>d2106100dac75d112347b30e88338b569289d7d2</id>
    </parent>
  </parents>
  <author>
    <name>shanti</name>
    <email>sems@sparqs-macbook-pro-15.local</email>
  </author>
  <url>http://github.com/sbraford/go/commit/4bd300c26fbc7c46b1fab851c60b9264a0739d10</url>
  <id>4bd300c26fbc7c46b1fab851c60b9264a0739d10</id>
  <committed-date>2008-10-05T22:14:28-07:00</committed-date>
  <authored-date>2008-10-05T22:14:28-07:00</authored-date>
  <message>install plugin working; some basic unit tests</message>
  <tree>f4b8a12618ee9006ad0b6aa5982b47eea01aac4e</tree>
  <committer>
    <name>shanti</name>
    <email>sems@sparqs-macbook-pro-15.local</email>
  </committer>
</commit>
