postmodern / sake forked from defunkt/sake

System wide Rake.

This URL has Read+Write access

sake / lib / sake.rb
100644 302 lines (264 sloc) 7.833 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
##
# Sake. Best served warm.
#
# >> Chris Wanstrath
# => chris@ozmm.org
 
require 'rubygems'
require 'rake'
require 'fileutils'
require 'open-uri'
 
begin
  gem 'ParseTree', '>=2.1.1'
  require 'parse_tree'
  gem 'ruby2ruby', '>=1.1.8'
  require 'ruby2ruby'
rescue LoadError
  puts "# Sake requires the ParseTree and ruby2ruby gems and Ruby >=1.8.6."
  exit
end
 
require 'sake/version'
require 'sake/tasks_array'
require 'sake/tasks_file'
require 'sake/task'
require 'sake/help'
require 'sake/pastie'
 
##
# Show all Sake tasks (but no local Rake tasks), optionally only those matching a pattern.
# $ sake -T
# $ sake -T db
#
# Show tasks in a Rakefile, optionally only those matching a pattern.
# $ sake -T file.rake
# $ sake -T file.rake db
#
# Install tasks from a Rakefile, optionally specifying specific tasks.
# $ sake -i Rakefile
# $ sake -i Rakefile db:remigrate
# $ sake -i Rakefile db:remigrate routes
#
# Examine the source of a Rake task.
# $ sake -e routes
#
# You can also examine the source of a task not yet installed.
# $ sake -e Rakefile db:remigrate
#
# Uninstall an installed task.
# $ sake -u db:remigrate
#
# Stores the source of a task into a pastie (http://pastie.caboo.se).
# Returns the url of the pastie to stdout.
# $ sake -P routes
#
# Can be passed one or more tasks.
#
# Invoke a Sake task.
# $ sake <taskname>
#
# 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
  ##
  # 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
 
  ##
  # 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
    if index = @args.index("--force")
      @args.delete("--force")
      @force = true
    end
    
    ##
    # Show Sake tasks in the store or in a file, optionally searching for a pattern.
    # $ sake -T
    # $ sake -T db
    # $ sake -T file.rake
    # $ sake -T file.rake db
    # Show all Sake tasks in the store or in a file, optionally searching for a pattern.
    # $ sake -Tv
    # $ sake -Tv db
    # $ sake -Tv file.rake
    # $ sake -Tv file.rake db
    if (index = @args.index('-T') || @args.index('-Tv')) || @args.empty?
      display_hidden = true if @args.index('-Tv')
      begin
        tasks = TasksFile.parse(@args[index + 1]).tasks
        pattern = @args[index + 2]
      rescue => parse_error
        tasks = Store.tasks.sort
        pattern = index ? @args[index + 1] : nil
      end
      output = show_tasks(tasks, pattern, display_hidden)
      if output.empty? and @args.size > 1 # show_tasks didn't show any tasks
        case parse_error
        when Errno::ENOENT, OpenURI::HTTPError
          die "# Can't find file (or task) `#{@args[index + 1]}'"
        when SecurityError
          die "# SecurityError parsing `#{@args[index + 1]}'"
        else
          die "# No matching tasks for `#{pattern}'" if pattern
        end
      end
      return output
 
    ##
    # Install a Rakefile or a single Rake task
    # $ sake -i Rakefile
    # $ sake -i Rakefile db:migrate
    elsif index = @args.index('-i')
      return install(index)
 
    ##
    # Uninstall one or more Rake tasks from the Sake store.
    elsif index = @args.index('-u')
      return uninstall(index)
 
    ##
    # Examine a Rake task
    # $ sake -e routes
    # $ sake -e Rakefile db:remigrate
    elsif index = @args.index('-e')
      die examine(index)
 
    ##
    # Save one or more tasks to Pastie (http://pastie.caboos.se)
    # then return the new Pastie's url
    # $ sake -P routes
    # $ sake -P Rakefile db:remigrate
    elsif index = @args.index('-P')
      die Pastie.paste(examine(index))
 
    ##
    # 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
 
    ##
    # Prints out the help screen.
    elsif @args.include? '-h' or @args.include? '--help'
      return Help.display
    end
 
    ##
    # Runs Rake proper, including our ~/.sake tasks.
    run_rake
  end
 
  private
 
  def show_tasks(tasks = [], pattern = nil, display_hidden = nil)
    Rake.application.show(tasks, pattern, display_hidden)
  end
 
  def install(index)
    die "# I need a Rakefile." unless file = @args[index+1]
 
    tasks = TasksFile.parse(file).tasks
 
    # We may want to install a specific task
    unless (target_tasks = @args[index + 2..-1]).empty?
      tasks = tasks.select { |task| target_tasks.include? task.name }
    end
 
    # No duplicates.
    tasks.each do |task|
      if Store.has_task?(task) && !@force
        puts "# Task `#{task}' already exists in #{Store.path}"
        next
      elsif Store.has_task?(task)
        puts "# Task `#{task}' already exists. Updating it."
        Store.remove_task(task)
      else
        puts "# Installing task `#{task}'"
      end
      
      Store.add_task task
    end
 
    # Commit.
    Store.save!
  end
 
  def uninstall(index)
    die "# -u option needs one or more installed tasks" if (tasks = @args[index+1..-1]).empty?
 
    tasks.each do |name|
      if task = Store.tasks[name]
        puts "# Uninstalling `#{task}'. Here it is, for reference:", task.to_ruby, ''
        Store.remove_task(task)
      else
        puts "# You don't have task `#{name}' installed.", ''
      end
    end
 
    Store.save!
  end
 
  ##
  # There is a lot of guesswork inside this method. Sorry.
  def examine(index)
    # Can be -e file task or -e task, which defaults to Store.path
    if @args[index + 2]
      file = @args[index + 1]
      task = @args[index + 2]
    else
      task = @args[index + 1]
    end
 
    # They didn't pass any args in, so just show the ~/.sake file
    unless task
      return Store.tasks.to_ruby
    end
 
    # Try to find the task we think they asked for.
    tasks = file ? TasksFile.parse(file).tasks : Store.tasks
 
    if tasks[task]
      return tasks[task].to_ruby
    end
 
    # Didn't find the task. See if it's a file and, if so, spit
    # it out.
    unless (tasks = TasksFile.parse(task).tasks).empty?
      return tasks.to_ruby
    end
 
    # Failure. On all counts.
    error = "# Can't find task (or file) `#{task}'"
    error << " in #{file}" if file
    die error
  end
 
  def serve_tasks
    require 'sake/server'
 
    Server.start(@args)
  end
 
  def version
    puts "sake, version #{VERSION}"
  end
 
  def run_rake
    import Sake::Store.path
    Rake.application.run
  end
end
 
def die(*message) # :nodoc:
  puts message
  exit
end
 
Sake.new(ARGV).run if $0 == __FILE__