<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,12 +1,5 @@
-./bin/sake
-./lib/Rakefile
-./lib/sake/action.rb
-./lib/sake/actions/default.rb
-./lib/sake/actions/install.rb
-./lib/sake/actions/version.rb
-./lib/sake/hacks.rb
-./lib/sake/rake_faker.rb
-./lib/sake/tasks.rb
-./lib/sake.rb
 ./Manifest.txt
 ./Rakefile
+./sake.rb
+./serve.rb
+./two</diff>
      <filename>Manifest.txt</filename>
    </modified>
    <modified>
      <diff>@@ -8,8 +8,8 @@ begin
 
   Echoe.new('sake', Sake::Version::String) do |p|
     p.rubyforge_name = 'sake'
-    p.summary = &quot;sake tastes great and helps maintain system-level Rake files&quot;
-    p.description = &quot;sake tastes great and helps maintain system-level Rake files&quot;
+    p.summary = &quot;Sake tastes great and helps maintain system-level Rake files.&quot;
+    p.description = &quot;Sake tastes great and helps maintain system-level Rake files.&quot;
     p.url = &quot;http://errtheblog.com/&quot;
     p.author = 'Chris Wanstrath'
     p.email = &quot;chris@ozmm.org&quot;</diff>
      <filename>Rakefile</filename>
    </modified>
    <modified>
      <diff>@@ -2,4 +2,4 @@
 require 'rubygems'
 require 'sake'
 
-Sake.new(ARGV).invoke
+Sake.new(ARGV).run</diff>
      <filename>bin/sake</filename>
    </modified>
    <modified>
      <diff>@@ -1,48 +1,403 @@
-#!/usr/bin/env ruby
+##
+# Sake.  Best served warm.
+#
+# &gt;&gt; Chris Wanstrath
+# =&gt; chris@ozmm.org
 
 require 'rubygems'
 require 'rake'
-require 'ruby2ruby'
+require 'fileutils'
+begin
+  require 'ruby2ruby'
+rescue LoadError
+  die &quot;=&gt; Sake requires the ruby2ruby gem.  Please install it.  Thanks!&quot;
+end
 
-$:.unshift File.dirname(__FILE__)
+##
+# Show all Sake tasks (but no local Rake tasks).
+#   $ sake -T
+#
+# Show tasks in a Rake file.
+#   $ sake -T file.rake
+#
+# Install all tasks in a Rake file or a single Rake task
+#   $ sake -i Rakefile
+#   $ sake -i Rakefile db:migrate
+#
+# Run a Sake task.
+#   $ sake &lt;taskname&gt;
+#
+# Some Sake tasks may depend on tasks which exist only locally.
+#
+# For instance, you may have a db:version sake task which depends
+# on the 'environment' Rake task.  The 'environment' Rake task is one
+# defined by Rails to load its environment.  This db:version task will
+# work when your current directory is within a Rails app because
+# Sake knows how to find Rake tasks.  This task will not work,
+# however, in any other directory (unless a task named 'environment' 
+# indeed exists).
+#
+# Sake can also serve its tasks over a network by launching a Mongrel handler.
+# Pass the -S switch to start Sake in server mode.
+#
+#   $ sake -S
+#
+# You can, of course, specify a port.
+#   $ sake -S -p 1111
+#
+# You can also daemonize your server for long term serving fun.
+#   $ sake -S -d
+#
+class Sake
+  module Version
+    Major  = '0'
+    Minor  = '1'
+    Tweak  = '0'
+    String = [ Major, Minor, Tweak ].join('.')
+  end
 
-require 'sake/rake_faker'
-require 'sake/hacks'
-require 'sake/tasks'
+  ##
+  # The `application' class, this is basically the controller
+  # which decides what to do then executes.
+  def initialize(args)
+    @args = args
+    Rake.application
+    Rake.application.options.silent = true
+  end
 
-require 'sake/action'
-Dir[File.dirname(__FILE__) + '/sake/actions/*'].each do |action|
-  require action
-end
+  ##
+  # This method figures out what to do and does it.
+  # Basically a big switch.  Note the seemingly random
+  # return statements: return if you don't want run_rake invoked.
+  # Some actions do want it invoked, however, so they don't return
+  # (like version, which prints a Sake version then trusts Rake to do
+  # likewise).
+  def run
+    ##
+    # Examine a Rake file.
+    # $ sake -T file.rake
+    if (index = @args.index('-T')) &amp;&amp; (file = @args[index+1]).is_file?
+      return show_tasks(TasksFile.new(file).tasks)
 
-class Sake
-  extend Tasks
+    ##
+    # Show all Sake tasks (but no local Rake tasks).
+    # $ sake -T
+    elsif index || @args.empty?
+      return show_tasks(Store.tasks.sort, @args[index.to_i+1])
+
+    ##
+    # Install a Rake file or a single Rake task
+    # $ sake -i Rakefile
+    # $ sake -i Rakefile db:migrate
+    elsif index = @args.index('-i')
+      return install(index)
 
-  def initialize(args = [])
-    @options = {
-      :args   =&gt; args,
-      :target =&gt; detect_target(args),
-      :source =&gt; detect_source(args)
-    }
+    ##
+    # Start a Mongrel handler which will serve local Rake tasks
+    # to anyone who wants them.
+    #
+    # $ sake -S
+    #
+    # Set a port
+    # $ sake -S -p 1111
+    #
+    # Daemonize
+    # $ sake -S -d
+    elsif @args.include? '-S'
+      return serve_tasks
+
+    ##
+    # Prints Sake and Rake versions.
+    elsif @args.include? '--version'
+      version
+    end
+
+    ##
+    # Runs Rake proper, including our ~/.sake tasks.
+    run_rake
+  end
+
+  private
+
+  def show_tasks(tasks = [], pattern = nil)
+    Rake.application.show(tasks, pattern)
   end
 
-  def invoke
-    Action.invoke(@options)
+  def install(index)
+    unless (file = @args[index+1]) &amp;&amp; file.is_file?
+      die &quot;=&gt; `#{file}' is not a Rakefile, sorry.&quot; 
+    end
+
+    tasks = TasksFile.new(file).tasks
+
+    # We may want to install a specific task
+    if target_task = @args[index + 2]
+      tasks = tasks.select { |task| task.name == target_task }
+    end
+
+    # No duplicates.
+    tasks.each do |task|
+      if Store.has_task? task
+        puts &quot;!! Task `#{task}' already exists in #{Store.path}&quot;
+      else
+        puts &quot;=&gt; Installing task `#{task}'&quot;
+        Store.add_task task
+      end
+    end
+
+    # Commit.
+    Store.save!
+  end
+
+  def serve_tasks
+    Server.start(@args)
+  end
+
+  def version
+    puts &quot;sake, version #{Version::String}&quot;
+  end
+
+  def run_rake
+    import Sake::Store.path
+    Rake.application.run
+  end
+
+  ##
+  # This class represents a Rake task file, in the traditional sense.
+  # It takes on parameter: the path to a Rake file.  When instantiated,
+  # it will read the file and parse out the rake tasks, storing them in
+  # a 'tasks' array.  This array can be accessed directly:
+  #
+  #   file = Sake::TasksFile.new('Rakefile')
+  #   puts file.tasks.inspect
+  class TasksFile
+    attr_reader :tasks
+
+    def initialize(file)
+      @namespace = []
+      @tasks     = []
+      @comment   = nil
+      instance_eval File.read(file) if file.is_file?
+    end
+
+    ##
+    # We fake out an approximation of the Rake DSL in order to build
+    # our tasks array.
+    private
+
+    ##
+    # Set a namespace for the duration of the block.  Namespaces can be 
+    # nested.
+    def namespace(name)
+      @namespace &lt;&lt; name
+      yield
+      @namespace.delete name
+    end
+
+    ##
+    # Describe the following task.
+    def desc(comment)
+      @comment = comment
+    end
+
+    ## 
+    # Define a task and any dependencies it may have.
+    def task(name, &amp;block)
+      # If we're passed a hash, we know it has one key (the name of
+      # the task) pointing to a single or multiple dependencies. 
+      if name.is_a? Hash
+        deps = name.values.first 
+        name = name.keys.first
+      end
+
+      # Our namespace is really just a convenience method.  Essentially,
+      # a namespace is just part of the task name.
+      name = [ @namespace, name ].flatten * ':'
+
+      # Sake's version of a rake task
+      task = Task.new(name, deps, @comment, &amp;block)
+
+      @tasks &lt;&lt; task
+
+      # We sucked up the last 'desc' declaration if it existed, so now clear
+      # it -- we don't want tasks without a description given one.
+      @comment = nil
+    end
+
+    public
+
+    ##
+    # Call to_ruby on all our tasks and return a concat'd string of them.
+    def to_ruby
+      @tasks.map { |task| task.to_ruby }.join(&quot;\n&quot;)
+    end
+
+    ##
+    # Add tasks to this TasksFile.  Can accept another TasksFile object or
+    # an array of Task objects.
+    def add_tasks(tasks)
+      Array(tasks.is_a?(TasksFile) ? tasks.tasks : tasks).each do |task|
+        add_task task
+      end
+    end
+
+    ##
+    # Single task version of add_tasks
+    def add_task(task)
+      @tasks &lt;&lt; task
+    end
+
+    ##
+    # Does this task exist?
+    def has_task?(task)
+      @tasks.map { |t| t.to_s }.include? task.to_s
+    end
+
+    ##
+    # Hunt for and remove a particular task.
+    def remove_task(task_name)
+      @tasks.reject! { |task| task.name == task_name }
+    end
   end
 
-  def self.tasks
-    sake_tasks
+  ##
+  # This is Sake's version of a Rake task.  Please handle with care.
+  class Task
+    attr_reader :name, :comment
+
+    def initialize(name, deps = nil, comment = nil, &amp;block)
+      @name    = name
+      @comment = comment
+      @deps    = Array(deps)
+      @body    = block
+    end
+
+    ##
+    # Turn ourselves back into Rake task plaintext.
+    def to_ruby
+      out = ''
+      out &lt;&lt; &quot;desc '#{@comment}'\n&quot; if @comment
+      out &lt;&lt; &quot;task '#{@name}'&quot;
+
+      if @deps.any?
+        deps = @deps.map { |dep| &quot;'#{dep}'&quot; }.join(', ')
+        out &lt;&lt; &quot; =&gt; [ #{deps} ]&quot; 
+      end
+
+      out &lt;&lt; &quot; do\n&quot;
+      
+      # get rid of the proc { / } lines
+      out &lt;&lt; @body.to_ruby.split(&quot;\n&quot;)[1...-1].join(&quot;\n&quot;)
+
+      out &lt;&lt; &quot;\nend\n&quot;
+    end
+
+    ##
+    # String-ish duck typing
+    def &lt;=&gt;(other)
+      to_s &lt;=&gt; other.to_s
+    end
+
+    def to_s; @name end
+    def inspect; @name.inspect end
   end
 
   ##
-  # Command line parsing
-  def detect_target(args)
-    args.detect { |arg| arg[/-|:\/\//].nil? }
+  # The store is, as of writing, a single Rake file: ~/.sake
+  # When we add new tasks, we just re-build this file.  Over
+  # and over.
+  module Store
+    extend self
+
+    ##
+    # Everything we can't catch gets sent to our tasks_file.
+    # Common examples are #tasks or #add_task.
+    def method_missing(*args, &amp;block)
+      tasks_file.send(*args, &amp;block)
+    end
+
+    def tasks_file
+      FileUtils.touch(path) unless path.is_file?
+      @tasks_file ||= TasksFile.new(path)
+    end
+
+    def path
+      File.join(File.expand_path('~'), '.sake')
+    end
+
+    def save!
+      File.open(path, 'w') do |file|
+        file.puts tasks_file.to_ruby
+      end
+    end
+  end
+end
+
+module Rake
+  class Application
+    ##
+    # Show the tasks as 'sake' tasks.
+    def printf(*args)
+      args[0].sub!('rake', 'sake') if args[0].is_a? String
+      super
+    end
+
+    ##
+    # Show tasks that don't have comments'
+    def display_tasks_and_comments(tasks = nil, pattern = nil)
+      tasks ||= self.tasks
+
+      if pattern ||= options.show_task_pattern
+        tasks = tasks.select { |t| t.name[pattern] || t.comment.to_s[pattern] }
+      end
+
+      width = tasks.collect { |t| t.name.length }.max
+
+      tasks.each do |t|
+        comment = &quot;   # #{t.comment}&quot; if t.comment
+        printf &quot;sake %-#{width}s#{comment}\n&quot;, t.name
+      end
+    end
+    alias_method :show, :display_tasks_and_comments
+
+    ##
+    # Run Sake even if no Rakefile exists in the current directory.
+    alias_method :sake_original_have_rakefile, :have_rakefile
+    def have_rakefile(*args)
+      @rakefile ||= ''
+      sake_original_have_rakefile(*args) || true
+    end
   end
 
-  def detect_source(args)
-    args.detect { |arg| arg[/(\w+:\/\/)/] }
+  class Task
+    ##
+    # We want only run a Sake task -- not any other matching
+    # or duplicate tasks.
+    def enhance(deps=nil, &amp;block)
+      @prerequisites |= deps if deps
+      @actions = [block] if block_given? 
+      self
+    end
   end
 end
 
-Sake.new(ARGV).invoke if $0 == __FILE__
+##
+# Hacks which give us &quot;Rakefile&quot;.is_file? 
+class String
+  def is_file?
+    File.exists? self
+  end
+end
+
+class Nil
+  def is_file?
+    false
+  end
+end
+
+def die(*message)
+  puts message 
+  exit
+end
+
+Sake.new(ARGV).run if $0 == __FILE__</diff>
      <filename>lib/sake.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>lib/Rakefile</filename>
    </removed>
    <removed>
      <filename>lib/sake/action.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/actions/default.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/actions/install.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/actions/serve.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/actions/version.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/hacks.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/rake_faker.rb</filename>
    </removed>
    <removed>
      <filename>lib/sake/tasks.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>da6740a48907dcc8097b0bd94b27c9ce2a96d02f</id>
    </parent>
  </parents>
  <author>
    <name>Chris Wanstrath</name>
    <email>chris@ozmm.org</email>
  </author>
  <url>http://github.com/defunkt/sake/commit/2b25483549a597747e067beebc67cb452247e9d6</url>
  <id>2b25483549a597747e067beebc67cb452247e9d6</id>
  <committed-date>2007-06-25T02:03:58-07:00</committed-date>
  <authored-date>2007-06-25T02:03:58-07:00</authored-date>
  <message>sake2 =&gt; sake</message>
  <tree>fe7fc9e420a0ed66cd5652940ef1462c4d443697</tree>
  <committer>
    <name>Chris Wanstrath</name>
    <email>chris@ozmm.org</email>
  </committer>
</commit>
