<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -8,6 +8,7 @@ rescue LoadError
   require 'bones'
 end
 
-Kernel.load Bones.libpath(%w[bones app.rb])
+Bones::App.initialize_plugins
+Bones::App.run
 
 # EOF</diff>
      <filename>bin/bones</filename>
    </modified>
    <modified>
      <diff>@@ -51,11 +51,8 @@ end  # module Bones
 
 begin
   $LOAD_PATH.unshift Bones.libpath
-  require 'bones/colors'
-  require 'bones/helpers'
-  require 'bones/gem_package_task'
-  require 'bones/annotation_extractor'
-  require 'bones/smtp_tls'
+  %w[colors helpers gem_package_task annotation_extractor smtp_tls app app/command app/file_manager].
+  each { |fn| require File.join('bones', fn) }
 
   Bones.config {}
   Loquacious.remove :gem, :file, :test</diff>
      <filename>lib/bones.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,175 +1,110 @@
 
-Main {
-  program 'bones'
-  version Bones::VERSION
-
-  synopsis 'bones command [options] [arguments]'
-
-  description &lt;&lt;-__
-Mr Bones is a handy tool that builds a skeleton for your new Ruby
-projects. The skeleton contains some starter code and a collection of
-rake tasks to ease the management and deployment of your source code.
-
-Usage:
-  bones -h/--help
-  bones command [options] [arguments]
-
-Examples:
-  bones create new_project
-  bones freeze -r git://github.com/fudgestudios/bort.git bort
-  bones create -s bort new_rails_project
-
-Commands:
-  bones create          create a new project from a skeleton
-  bones freeze          create a new skeleton in ~/.mrbones/
-  bones unfreeze        remove a skeleton from ~/.mrbones/
-  bones info            show information about available skeletons
-
-Further Help:
-  Each command has a '--help' option that will provide detailed
-  information for that command.
-
-  http://gemcutter.org/gems/bones
-  __
-
-  def run() help!; end
-
-  # --------------------------------------------------------------------------
-  mode 'create' do
-    synopsis 'bones create [options] &lt;project_name&gt;'
-    description &lt;&lt;-__
-Create a new project from a Mr Bones project skeleton. The skeleton can
-be the default project skeleton from the Mr Bones gem or one of the named
-skeletons found in the '~/.mrbones/' folder. A git or svn repository can
-be used as the skeleton if the '--repository' flag is given.
-    __
-
-    argument('project_name') {
-      arity 1
-      description 'Name of the new Ruby project to create.'
-    }
-
-    option('d', 'directory') {
-      argument :required
-      description 'Project directory to create (defaults to project_name).'
-    }
-
-    option('s', 'skeleton') {
-      argument :required
-      description 'Project skeleton to use.'
-    }
-
-    option('r', 'repository') {
-      argument :required
-      description 'svn or git repository path.'
-    }
-
-    option('git') {
-      description 'Initialize a git repository for the project.'
-    }
-
-    option('github') {
-      argument :optional
-      description 'Create a new GitHub project. You can provide an optional project description. The --git option is implied when the --github option is present.'
-    }
-
-    option('v', 'verbose') {
-      description 'Enable verbose output.'
-    }
-
-    def run
-      Bones::App::CreateCommand.run params
-
-    rescue Bones::App::CreateCommand::Error, Git::GitExecuteError =&gt; err
-      $stderr.puts err.message
-      abort
-    end
-  end
+require 'net/http'
+require 'uri'
 
-  # --------------------------------------------------------------------------
-  mode 'freeze' do
-    synopsis 'bones freeze [options] [skeleton_name]'
-    description &lt;&lt;-__
-Freeze the project skeleton to the current Mr Bones project skeleton.
-If a name is not given, then the default name &quot;data&quot; will be used.
-Optionally a git or svn repository can be frozen as the project
-skeleton.
-    __
-
-    argument('skeleton_name') {
-      arity 1
-      default 'data'
-      description 'Name of the skeleton to create.'
-    }
-
-    option('r', 'repository') {
-      argument :required
-      description 'svn or git repository path.'
-    }
-
-    option('v', 'verbose') {
-      description 'Enable verbose output.'
-    }
-
-    def run
-      Bones::App::FreezeCommand.run params
-    end
+module Bones::App
+  extend LittlePlugger(:path =&gt; 'bones/app', :module =&gt; Bones::App)
+  disregard_plugins :error, :main, :command, :file_manager
+
+  Error = Class.new(StandardError)
+
+  # Create a new instance of Main, and run the +bones+ application given
+  # the command line _args_.
+  #
+  def self.run( args = nil )
+    args ||= ARGV.dup.map! { |v| v.dup }
+    ::Bones::App::Main.new.run args
   end
 
-  # --------------------------------------------------------------------------
-  mode 'unfreeze' do
-    synopsis 'bones unfreeze [skeleton_name]'
-    description &lt;&lt;-__
-Removes the named skeleton from the '~/.mrbones/' folder. If a name is
-not given then the default skeleton is removed.
-    __
-
-    argument('skeleton_name') {
-      arity 1
-      default 'data'
-      description 'Name of the skeleton to remove.'
-    }
-
-    option('v', 'verbose') {
-      description 'Enable verbose output.'
-    }
-
-    def run
-      Bones::App::UnfreezeCommand.run params
+  class Main
+    attr_reader :stdout
+    attr_reader :stderr
+
+    # Create a new Main application instance. Options can be passed to
+    # configuret he stdout and stderr IO streams (very useful for testing).
+    #
+    def initialize( opts = {} )
+      opts[:stdout] ||= $stdout
+      opts[:stderr] ||= $stderr
+
+      @opts = opts
+      @stdout = opts[:stdout]
+      @stderr = opts[:stderr]
     end
-  end
 
-  # --------------------------------------------------------------------------
-  mode 'info' do
-    synopsis 'bones info'
-    description 'Shows information about available skeletons.'
+    # Parse the desired user command and run that command object.
+    #
+    def run( args )
+      plugins = ::Bones::App.plugins
+      commands = plugins.keys.map! {|k| k.to_s.split('_').first}
+
+      cmd_str = args.shift
+      cmd = case cmd_str
+        when *commands
+          key = (cmd_str + '_command').to_sym
+          plugins[key].new @opts
+        when nil, '-h', '--help'
+          help
+        when '-v', '--version'
+          stdout.puts &quot;Mr Bones v#{::Bones::VERSION}&quot;
+          nil
+        else
+          raise Error, &quot;Unknown command #{cmd_str.inspect}&quot;
+        end
+
+      if cmd
+        cmd.parse args
+        cmd.run
+      end
+
+    rescue Bones::App::Error =&gt; err
+      stderr.puts &quot;ERROR:  While executing bones ...&quot;
+      stderr.puts &quot;    #{err.message}&quot;
+      exit 1
+    rescue StandardError =&gt; err
+      stderr.puts &quot;ERROR:  While executing bones ... (#{err.class})&quot;
+      stderr.puts &quot;    #{err.to_s}&quot;
+      exit 1
+    end
 
-    def run
-      Bones::App::InfoCommand.run params
+    # Show the toplevel Mr Bones help message.
+    #
+    def help
+      stdout.puts &lt;&lt;-MSG
+NAME
+  bones v#{::Bones::VERSION}
+
+DESCRIPTION
+  Mr Bones is a handy tool that builds a skeleton for your new Ruby
+  projects. The skeleton contains some starter code and a collection of
+  rake tasks to ease the management and deployment of your source code.
+
+  Usage:
+    bones -h/--help
+    bones -v/--version
+    bones command [options] [arguments]
+
+  Examples:
+    bones create new_project
+    bones freeze -r git://github.com/fudgestudios/bort.git bort
+    bones create -s bort new_rails_project
+
+  Commands:
+    bones create          create a new project from a skeleton
+    bones freeze          create a new skeleton in ~/.mrbones/
+    bones unfreeze        remove a skeleton from ~/.mrbones/
+    bones info            show information about available skeletons
+
+  Further Help:
+    Each command has a '--help' option that will provide detailed
+    information for that command.
+
+    http://github.com/TwP/bones
+
+      MSG
     end
-  end
-}
-
-
-BEGIN {
-  require 'main'
-  require 'erb'
-  require 'net/http'
-  require 'uri'
-
-  module Bones::App; end
-
-  begin
-    $LOAD_PATH.unshift Bones.libpath
-    require 'bones/app/command'
-    require 'bones/app/file_manager'
-    require 'bones/app/create_command'
-    require 'bones/app/freeze_command'
-    require 'bones/app/unfreeze_command'
-    require 'bones/app/info_command'
-  ensure
-    $LOAD_PATH.shift
-  end
-}
+
+  end  # class Main
+end  # module Bones::App
 
 # EOF</diff>
      <filename>lib/bones/app.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,52 +1,49 @@
 
-module Bones::App
+class Bones::App::Command
 
-class Command
+  attr_reader :stdout
+  attr_reader :stderr
+  attr_reader :config
 
-  # Run the current command using the given Main _params_ table.
-  #
-  def self.run( params )
-    new(params).run
-  end
-
-  attr_reader :options
+  def initialize( opts = {} )
+    @stdout = opts[:stdout] || $stdout
+    @stderr = opts[:stderr] || $stderr
 
-  #
-  #
-  def initialize( params, out = STDOUT, err = STDERR )
-    @out = out
-    @err = err
-    normalize params
+    @config = {
+      :skeleton_dir =&gt; File.join(mrbones_dir, 'data'),
+      :verbose =&gt; false,
+      :name =&gt; nil,
+      :output_dir =&gt; nil
+    }
+    @config[:skeleton_dir] = ::Bones.path('data') unless test(?d, skeleton_dir)
   end
 
-  # Implemented by subclasses.
-  #
-  def run
+  def run( args )
     raise NotImplementedError
   end
 
   # The output directory where files will be written.
   #
   def output_dir
-    options[:output_dir]
+    @config[:output_dir]
   end
 
   # The directory where the project skeleton is located.
   #
   def skeleton_dir
-    options[:skeleton_dir]
+    @config[:skeleton_dir]
   end
 
   # The project name from the command line.
   #
   def name
-    options[:name]
+    @config[:name]
   end
 
   # A git or svn repository URL from the command line.
   #
   def repository
-    return options[:repository] if options.has_key? :repository
+    return @config[:repository] if @config.has_key? :repository
     return IO.read(skeleton_dir).strip if skeleton_dir and test(?f, skeleton_dir)
     nil
   end
@@ -54,10 +51,10 @@ class Command
   # Returns +true+ if the user has requested verbose messages.
   #
   def verbose?
-    options[:verbose]
+    @config[:verbose]
   end
 
-  # Returns the .mrbones resource directory in the user's home directory.
+  # Returns the '.mrbones' resource directory in the user's home directory.
   #
   def mrbones_dir
     return @mrbones_dir if defined? @mrbones_dir
@@ -66,36 +63,6 @@ class Command
     @mrbones_dir = File.expand_path(path)
   end
 
-  # Take the Main _params_ and convert them into an options hash usable by the
-  # command objects.
-  #
-  def normalize( params )
-    @params = params.to_options
-    @options = {
-      :name         =&gt; @params['project_name'] || @params['skeleton_name'],
-      :verbose      =&gt; @params['verbose'] ? true : false,
-      :repository   =&gt; @params['repository'],
-      :output_dir   =&gt; @params['directory'],
-      :skeleton_dir =&gt; nil
-    }
-
-    if value = @params['skeleton']
-      path = File.join(mrbones_dir, value)
-      if test(?e, path)
-        @options[:skeleton_dir] = path
-      elsif test(?e, value)
-        @options[:skeleton_dir] = value
-      else
-        raise ArgumentError, &quot;Unknown skeleton '#{value}'&quot;
-      end
-    else
-      @options[:skeleton_dir] = File.join(mrbones_dir, 'data')
-    end
-
-    @options[:skeleton_dir] = ::Bones.path('data') unless test(?d, skeleton_dir)
-    @options[:output_dir] ||= @options[:name]
-  end
-
   # Run a block of code in the given directory.
   #
   def in_directory( dir )
@@ -106,7 +73,109 @@ class Command
     FileUtils.cd pwd
   end
 
-end  # class Command
-end  # module Bones::App
+  #
+  #
+  def standard_options
+    Command.standard_options
+  end
+
+  #
+  #
+  def parse( args )
+    opts = OptionParser.new
+
+    opts.banner = 'NAME'
+    opts.separator &quot;  bones v#{::Bones::VERSION}&quot;
+    opts.separator ''
+
+    if self.class.synopsis
+      opts.separator 'SYNOPSIS'
+      self.class.synopsis.split(&quot;\n&quot;).each { |line| opts.separator &quot;  #{line.strip}&quot; }
+      opts.separator ''
+    end
+
+    if self.class.description
+      opts.separator 'DESCRIPTION'
+      self.class.description.split(&quot;\n&quot;).each { |line| opts.separator &quot;  #{line.strip}&quot; }
+      opts.separator ''
+    end
+
+    if self.class.options and not self.class.options.empty?
+      opts.separator 'PARAMETERS'
+      self.class.options.each { |option|
+        case option
+        when Array; opts.on(*option)
+        when String; opts.separator(option)
+        else opts.separator('') end
+      }
+      opts.separator ''
+    end
+
+    opts.separator '  Common Options:'
+    opts.on_tail( '-h', '--help', 'show this message' ) {
+      stdout.puts opts
+      exit
+    }
+    opts.on_tail ''
+
+    opts.parse! args
+    return opts
+  end
+
+  #
+  #
+  def self.standard_options
+    @standard_options ||= {
+      :verbose =&gt; ['-v', '--verbose', 'Enable verbose output.',
+          lambda { config[:verbose] = true }],
+
+      :directory =&gt; ['-d', '--directory DIRECTORY', String,
+          'Project directory to create (defaults to project_name).',
+          lambda { |value| config[:output_dir] = value }],
+
+      :skeleton =&gt; ['-s', '--skeleton NAME', String,
+          'Project skeleton to use.',
+          lambda { |value|
+            path = File.join(mrbones_dir, value)
+            if test(?e, value)
+              config[:skeleton_dir] = value
+            elsif test(?e, path)
+              config[:skeleton_dir] = path
+            else
+              raise ArgumentError, &quot;Unknown skeleton '#{value}'.&quot;
+            end
+          }],
+
+      :repository =&gt; ['-r', '--repository URL', String,
+          'svn or git repository path.',
+          lambda { |value| config[:repository] = value }]
+    }
+  end
+
+  module ClassMethods
+    def synopsis( *args )
+      @synopsis = args.join(&quot;\n&quot;) unless args.empty?
+      @synopsis
+    end
+
+    def description( *args )
+      @description = args.join(&quot;\n&quot;) unless args.empty?
+      @description
+    end
+
+    def option( *args )
+      options &lt;&lt; args.flatten
+    end
+
+    def options
+      @options ||= []
+    end
+  end
+
+  def self.inherited( other )
+    other.extend ClassMethods
+  end
+
+end  # class Bones::App::Command
 
 # EOF</diff>
      <filename>lib/bones/app/command.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,36 +1,57 @@
 
 module Bones::App
-
 class CreateCommand &lt; Command
 
-  Error = Class.new(StandardError)
+  def self.initialize_create_command
+    synopsis 'bones create [options] &lt;project_name&gt;'
+
+    description &lt;&lt;-__
+Create a new project from a Mr Bones project skeleton. The skeleton can
+be the default project skeleton from the Mr Bones gem or one of the named
+skeletons found in the '~/.mrbones/' folder. A git or svn repository can
+be used as the skeleton if the '--repository' flag is given.
+    __
+
+    option(standard_options[:directory])
+    option(standard_options[:skeleton])
+    option(standard_options[:repository])
+    option(standard_options[:verbose])
+  end
 
   def run
     raise Error, &quot;Output directory #{output_dir.inspect} already exists.&quot; if test ?e, output_dir
 
     copy_files
 
-    pwd = File.expand_path(FileUtils.pwd)
     msg = &quot;Created '#{name}'&quot;
     msg &lt;&lt; &quot; in directory '#{output_dir}'&quot; if name != output_dir
-    @out.puts msg
+    stdout.puts msg
 
     in_directory(output_dir) {
-      initialize_git if git?
-      initialize_github if github?
-
       break unless test ?f, 'Rakefile'
-      @out.puts 'Now you need to fix these files'
+      stdout.puts 'Now you need to fix these files'
       system &quot;#{::Bones::RUBY} -S rake notes&quot;
     }
   end
 
+  def parse( args )
+    opts = super args
+
+    config[:name] = args.empty? ? nil : args.join('_')
+    config[:output_dir] = name if output_dir.nil?
+
+    if name.nil?
+      stdout.puts opts
+      exit 1
+    end
+  end
+
   def copy_files
     fm = FileManager.new(
       :source =&gt; repository || skeleton_dir,
       :destination =&gt; output_dir,
-      :stdout =&gt; @out,
-      :stderr =&gt; @err,
+      :stdout =&gt; stdout,
+      :stderr =&gt; stderr,
       :verbose =&gt; verbose?
     )
 
@@ -50,77 +71,6 @@ class CreateCommand &lt; Command
     raise Error, msg
   end
 
-  def git?
-    @params['git'] or @params['github']
-  end
-
-  def initialize_git
-    File.rename('.bnsignore', '.gitignore') if test ?f, '.bnsignore'
-
-    author = Git.global_config['user.name']
-    email  = Git.global_config['user.email']
-
-    if test ?f, 'Rakefile'
-      lines = File.readlines 'Rakefile'
-
-      lines.each do |line|
-        case line
-        when %r/^\s*authors\s+/
-          line.replace &quot;  authors  '#{author}'&quot; unless author.nil? or line !~ %r/FIXME/
-        when %r/^\s*email\s+/
-          line.replace &quot;  email  '#{email}'&quot; unless email.nil? or line !~ %r/FIXME/
-        when %r/^\s*url\s+/
-          next unless github?
-          url = github_url
-          line.replace &quot;  url  '#{url}'&quot; unless url.nil? or line !~ %r/FIXME/
-        when %r/^\s*\}\s*$/
-          line.insert 0, &quot;  ignore_file  '.gitignore'\n&quot; if test ?f, '.gitignore'
-        end
-      end
-
-      File.open('Rakefile', 'w') {|fd| fd.puts lines}
-    end
-
-    @git = Git.init
-    @git.add
-    @git.commit &quot;Initial commit to #{name}.&quot;
-  end
-
-  def github?
-    @params['github']
-  end
-
-  def initialize_github
-    user = Git.global_config['github.user']
-    token = Git.global_config['github.token']
-
-    raise Error, 'A GitHub username was not found in the global configuration.' unless user
-    raise Error, 'A GitHub token was not found in the global configuration.' unless token
-
-    Net::HTTP.post_form(
-        URI.parse('http://github.com/api/v2/yaml/repos/create'),
-        'login' =&gt; user,
-        'token' =&gt; token,
-        'name' =&gt; name,
-        'description' =&gt; description
-    )
-
-    @git.add_remote 'origin', &quot;git@github.com:#{user}/#{name}.git&quot;
-    @git.config 'branch.master.remote', 'origin'
-    @git.config 'branch.master.merge', 'refs/heads/master'
-    @git.push 'origin'
-  end
-
-  def github_url
-    user = Git.global_config['github.user']
-    return unless user
-    &quot;http://github.com/#{user}/#{name}&quot;
-  end
-
-  def description
-    String === @params['github'] ? @params['github'] : nil
-  end
-
 end  # class CreateCommand
 end  # module Bones::App
 </diff>
      <filename>lib/bones/app/create_command.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 
-module Bones::App
+require 'erb'
 
-class FileManager
+module Bones::App::FileManager
 
   Error = Class.new(StandardError)
 
@@ -167,7 +167,6 @@ class FileManager
     FileUtils.chmod(File.stat(src).mode, dst)
   end
 
-end  # class FileManager
-end  # module Bones::App
+end  # module Bones::App::FileManager
 
 # EOF</diff>
      <filename>lib/bones/app/file_manager.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>d2e51588c176e1c6cc378f2300a21b8aed3bb2b4</id>
    </parent>
  </parents>
  <author>
    <name>Tim Pease</name>
    <email>tim.pease@gmail.com</email>
  </author>
  <url>http://github.com/TwP/bones/commit/a27f7cbf1b49df43dda89b3ecc5d50d43ca7fdd3</url>
  <id>a27f7cbf1b49df43dda89b3ecc5d50d43ca7fdd3</id>
  <committed-date>2009-11-05T10:53:40-08:00</committed-date>
  <authored-date>2009-11-05T10:53:40-08:00</authored-date>
  <message>Refactor command line parsing to use command plugins.

The Main gem (awesome as it is) does not support the concept of dynamically
building up the command modes. I wouuld like users to be able to add new
command modes or modify existing modes. Reverting back to using optparse and
some inherited magic to make this happen.</message>
  <tree>a387f08c2b5e9151e04ad4071f640fb126a8d031</tree>
  <committer>
    <name>Tim Pease</name>
    <email>tim.pease@gmail.com</email>
  </committer>
</commit>
