public
Description: System wide Rake.
Homepage: http://errtheblog.com/posts/60-sake-bomb
Clone URL: git://github.com/defunkt/sake.git
sake2 => sake
defunkt (author)
Mon Jun 25 02:03:58 -0700 2007
commit  2b25483549a597747e067beebc67cb452247e9d6
tree    fe7fc9e420a0ed66cd5652940ef1462c4d443697
parent  da6740a48907dcc8097b0bd94b27c9ce2a96d02f
  • Manifest.txt
  • Rakefile
  • bin/sake
  • lib/Rakefile
  • lib/sake.rb
  • lib/sake/action.rb
  • lib/sake/actions/default.rb
  • lib/sake/actions/install.rb
  • lib/sake/actions/serve.rb
  • lib/sake/actions/version.rb
  • lib/sake/hacks.rb
  • lib/sake/rake_faker.rb
  • lib/sake/tasks.rb
...
1
2
3
4
5
6
7
8
9
10
11
12
 
 
 
...
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
0
@@ -1,12 +1,5 @@
0
-./bin/sake
0
-./lib/Rakefile
0
-./lib/sake/action.rb
0
-./lib/sake/actions/default.rb
0
-./lib/sake/actions/install.rb
0
-./lib/sake/actions/version.rb
0
-./lib/sake/hacks.rb
0
-./lib/sake/rake_faker.rb
0
-./lib/sake/tasks.rb
0
-./lib/sake.rb
0
 ./Manifest.txt
0
 ./Rakefile
0
+./sake.rb
0
+./serve.rb
0
+./two
...
8
9
10
11
12
 
 
13
14
15
...
8
9
10
 
 
11
12
13
14
15
0
@@ -8,8 +8,8 @@ begin
0
 
0
   Echoe.new('sake', Sake::Version::String) do |p|
0
     p.rubyforge_name = 'sake'
0
- p.summary = "sake tastes great and helps maintain system-level Rake files"
0
- p.description = "sake tastes great and helps maintain system-level Rake files"
0
+ p.summary = "Sake tastes great and helps maintain system-level Rake files."
0
+ p.description = "Sake tastes great and helps maintain system-level Rake files."
0
     p.url = "http://errtheblog.com/"
0
     p.author = 'Chris Wanstrath'
0
     p.email = "chris@ozmm.org"
...
2
3
4
5
 
...
2
3
4
 
5
0
@@ -2,4 +2,4 @@
0
 require 'rubygems'
0
 require 'sake'
0
 
0
-Sake.new(ARGV).invoke
0
+Sake.new(ARGV).run
...
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
 
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
303
304
305
 
 
 
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
 
 
372
373
374
375
376
377
378
379
380
381
382
383
 
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
0
@@ -1,48 +1,403 @@
0
-#!/usr/bin/env ruby
0
+##
0
+# Sake. Best served warm.
0
+#
0
+# >> Chris Wanstrath
0
+# => chris@ozmm.org
0
 
0
 require 'rubygems'
0
 require 'rake'
0
-require 'ruby2ruby'
0
+require 'fileutils'
0
+begin
0
+ require 'ruby2ruby'
0
+rescue LoadError
0
+ die "=> Sake requires the ruby2ruby gem. Please install it. Thanks!"
0
+end
0
 
0
-$:.unshift File.dirname(__FILE__)
0
+##
0
+# Show all Sake tasks (but no local Rake tasks).
0
+# $ sake -T
0
+#
0
+# Show tasks in a Rake file.
0
+# $ sake -T file.rake
0
+#
0
+# Install all tasks in a Rake file or a single Rake task
0
+# $ sake -i Rakefile
0
+# $ sake -i Rakefile db:migrate
0
+#
0
+# Run a Sake task.
0
+# $ sake <taskname>
0
+#
0
+# Some Sake tasks may depend on tasks which exist only locally.
0
+#
0
+# For instance, you may have a db:version sake task which depends
0
+# on the 'environment' Rake task. The 'environment' Rake task is one
0
+# defined by Rails to load its environment. This db:version task will
0
+# work when your current directory is within a Rails app because
0
+# Sake knows how to find Rake tasks. This task will not work,
0
+# however, in any other directory (unless a task named 'environment'
0
+# indeed exists).
0
+#
0
+# Sake can also serve its tasks over a network by launching a Mongrel handler.
0
+# Pass the -S switch to start Sake in server mode.
0
+#
0
+# $ sake -S
0
+#
0
+# You can, of course, specify a port.
0
+# $ sake -S -p 1111
0
+#
0
+# You can also daemonize your server for long term serving fun.
0
+# $ sake -S -d
0
+#
0
+class Sake
0
+ module Version
0
+ Major = '0'
0
+ Minor = '1'
0
+ Tweak = '0'
0
+ String = [ Major, Minor, Tweak ].join('.')
0
+ end
0
 
0
-require 'sake/rake_faker'
0
-require 'sake/hacks'
0
-require 'sake/tasks'
0
+ ##
0
+ # The `application' class, this is basically the controller
0
+ # which decides what to do then executes.
0
+ def initialize(args)
0
+ @args = args
0
+ Rake.application
0
+ Rake.application.options.silent = true
0
+ end
0
 
0
-require 'sake/action'
0
-Dir[File.dirname(__FILE__) + '/sake/actions/*'].each do |action|
0
- require action
0
-end
0
+ ##
0
+ # This method figures out what to do and does it.
0
+ # Basically a big switch. Note the seemingly random
0
+ # return statements: return if you don't want run_rake invoked.
0
+ # Some actions do want it invoked, however, so they don't return
0
+ # (like version, which prints a Sake version then trusts Rake to do
0
+ # likewise).
0
+ def run
0
+ ##
0
+ # Examine a Rake file.
0
+ # $ sake -T file.rake
0
+ if (index = @args.index('-T')) && (file = @args[index+1]).is_file?
0
+ return show_tasks(TasksFile.new(file).tasks)
0
 
0
-class Sake
0
- extend Tasks
0
+ ##
0
+ # Show all Sake tasks (but no local Rake tasks).
0
+ # $ sake -T
0
+ elsif index || @args.empty?
0
+ return show_tasks(Store.tasks.sort, @args[index.to_i+1])
0
+
0
+ ##
0
+ # Install a Rake file or a single Rake task
0
+ # $ sake -i Rakefile
0
+ # $ sake -i Rakefile db:migrate
0
+ elsif index = @args.index('-i')
0
+ return install(index)
0
 
0
- def initialize(args = [])
0
- @options = {
0
- :args => args,
0
- :target => detect_target(args),
0
- :source => detect_source(args)
0
- }
0
+ ##
0
+ # Start a Mongrel handler which will serve local Rake tasks
0
+ # to anyone who wants them.
0
+ #
0
+ # $ sake -S
0
+ #
0
+ # Set a port
0
+ # $ sake -S -p 1111
0
+ #
0
+ # Daemonize
0
+ # $ sake -S -d
0
+ elsif @args.include? '-S'
0
+ return serve_tasks
0
+
0
+ ##
0
+ # Prints Sake and Rake versions.
0
+ elsif @args.include? '--version'
0
+ version
0
+ end
0
+
0
+ ##
0
+ # Runs Rake proper, including our ~/.sake tasks.
0
+ run_rake
0
+ end
0
+
0
+ private
0
+
0
+ def show_tasks(tasks = [], pattern = nil)
0
+ Rake.application.show(tasks, pattern)
0
   end
0
 
0
- def invoke
0
- Action.invoke(@options)
0
+ def install(index)
0
+ unless (file = @args[index+1]) && file.is_file?
0
+ die "=> `#{file}' is not a Rakefile, sorry."
0
+ end
0
+
0
+ tasks = TasksFile.new(file).tasks
0
+
0
+ # We may want to install a specific task
0
+ if target_task = @args[index + 2]
0
+ tasks = tasks.select { |task| task.name == target_task }
0
+ end
0
+
0
+ # No duplicates.
0
+ tasks.each do |task|
0
+ if Store.has_task? task
0
+ puts "!! Task `#{task}' already exists in #{Store.path}"
0
+ else
0
+ puts "=> Installing task `#{task}'"
0
+ Store.add_task task
0
+ end
0
+ end
0
+
0
+ # Commit.
0
+ Store.save!
0
+ end
0
+
0
+ def serve_tasks
0
+ Server.start(@args)
0
+ end
0
+
0
+ def version
0
+ puts "sake, version #{Version::String}"
0
+ end
0
+
0
+ def run_rake
0
+ import Sake::Store.path
0
+ Rake.application.run
0
+ end
0
+
0
+ ##
0
+ # This class represents a Rake task file, in the traditional sense.
0
+ # It takes on parameter: the path to a Rake file. When instantiated,
0
+ # it will read the file and parse out the rake tasks, storing them in
0
+ # a 'tasks' array. This array can be accessed directly:
0
+ #
0
+ # file = Sake::TasksFile.new('Rakefile')
0
+ # puts file.tasks.inspect
0
+ class TasksFile
0
+ attr_reader :tasks
0
+
0
+ def initialize(file)
0
+ @namespace = []
0
+ @tasks = []
0
+ @comment = nil
0
+ instance_eval File.read(file) if file.is_file?
0
+ end
0
+
0
+ ##
0
+ # We fake out an approximation of the Rake DSL in order to build
0
+ # our tasks array.
0
+ private
0
+
0
+ ##
0
+ # Set a namespace for the duration of the block. Namespaces can be
0
+ # nested.
0
+ def namespace(name)
0
+ @namespace << name
0
+ yield
0
+ @namespace.delete name
0
+ end
0
+
0
+ ##
0
+ # Describe the following task.
0
+ def desc(comment)
0
+ @comment = comment
0
+ end
0
+
0
+ ##
0
+ # Define a task and any dependencies it may have.
0
+ def task(name, &block)
0
+ # If we're passed a hash, we know it has one key (the name of
0
+ # the task) pointing to a single or multiple dependencies.
0
+ if name.is_a? Hash
0
+ deps = name.values.first
0
+ name = name.keys.first
0
+ end
0
+
0
+ # Our namespace is really just a convenience method. Essentially,
0
+ # a namespace is just part of the task name.
0
+ name = [ @namespace, name ].flatten * ':'
0
+
0
+ # Sake's version of a rake task
0
+ task = Task.new(name, deps, @comment, &block)
0
+
0
+ @tasks << task
0
+
0
+ # We sucked up the last 'desc' declaration if it existed, so now clear
0
+ # it -- we don't want tasks without a description given one.
0
+ @comment = nil
0
+ end
0
+
0
+ public
0
+
0
+ ##
0
+ # Call to_ruby on all our tasks and return a concat'd string of them.
0
+ def to_ruby
0
+ @tasks.map { |task| task.to_ruby }.join("\n")
0
+ end
0
+
0
+ ##
0
+ # Add tasks to this TasksFile. Can accept another TasksFile object or
0
+ # an array of Task objects.
0
+ def add_tasks(tasks)
0
+ Array(tasks.is_a?(TasksFile) ? tasks.tasks : tasks).each do |task|
0
+ add_task task
0
+ end
0
+ end
0
+
0
+ ##
0
+ # Single task version of add_tasks
0
+ def add_task(task)
0
+ @tasks << task
0
+ end
0
+
0
+ ##
0
+ # Does this task exist?
0
+ def has_task?(task)
0
+ @tasks.map { |t| t.to_s }.include? task.to_s
0
+ end
0
+
0
+ ##
0
+ # Hunt for and remove a particular task.
0
+ def remove_task(task_name)
0
+ @tasks.reject! { |task| task.name == task_name }
0
+ end
0
   end
0
 
0
- def self.tasks
0
- sake_tasks
0
+ ##
0
+ # This is Sake's version of a Rake task. Please handle with care.
0
+ class Task
0
+ attr_reader :name, :comment
0
+
0
+ def initialize(name, deps = nil, comment = nil, &block)
0
+ @name = name
0
+ @comment = comment
0
+ @deps = Array(deps)
0
+ @body = block
0
+ end
0
+
0
+ ##
0
+ # Turn ourselves back into Rake task plaintext.
0
+ def to_ruby
0
+ out = ''
0
+ out << "desc '#{@comment}'\n" if @comment
0
+ out << "task '#{@name}'"
0
+
0
+ if @deps.any?
0
+ deps = @deps.map { |dep| "'#{dep}'" }.join(', ')
0
+ out << " => [ #{deps} ]"
0
+ end
0
+
0
+ out << " do\n"
0
+
0
+ # get rid of the proc { / } lines
0
+ out << @body.to_ruby.split("\n")[1...-1].join("\n")
0
+
0
+ out << "\nend\n"
0
+ end
0
+
0
+ ##
0
+ # String-ish duck typing
0
+ def <=>(other)
0
+ to_s <=> other.to_s
0
+ end
0
+
0
+ def to_s; @name end
0
+ def inspect; @name.inspect end
0
   end
0
 
0
   ##
0
- # Command line parsing
0
- def detect_target(args)
0
- args.detect { |arg| arg[/-|:\/\//].nil? }
0
+ # The store is, as of writing, a single Rake file: ~/.sake
0
+ # When we add new tasks, we just re-build this file. Over
0
+ # and over.
0
+ module Store
0
+ extend self
0
+
0
+ ##
0
+ # Everything we can't catch gets sent to our tasks_file.
0
+ # Common examples are #tasks or #add_task.
0
+ def method_missing(*args, &block)
0
+ tasks_file.send(*args, &block)
0
+ end
0
+
0
+ def tasks_file
0
+ FileUtils.touch(path) unless path.is_file?
0
+ @tasks_file ||= TasksFile.new(path)
0
+ end
0
+
0
+ def path
0
+ File.join(File.expand_path('~'), '.sake')
0
+ end
0
+
0
+ def save!
0
+ File.open(path, 'w') do |file|
0
+ file.puts tasks_file.to_ruby
0
+ end
0
+ end
0
+ end
0
+end
0
+
0
+module Rake
0
+ class Application
0
+ ##
0
+ # Show the tasks as 'sake' tasks.
0
+ def printf(*args)
0
+ args[0].sub!('rake', 'sake') if args[0].is_a? String
0
+ super
0
+ end
0
+
0
+ ##
0
+ # Show tasks that don't have comments'
0
+ def display_tasks_and_comments(tasks = nil, pattern = nil)
0
+ tasks ||= self.tasks
0
+
0
+ if pattern ||= options.show_task_pattern
0
+ tasks = tasks.select { |t| t.name[pattern] || t.comment.to_s[pattern] }
0
+ end
0
+
0
+ width = tasks.collect { |t| t.name.length }.max
0
+
0
+ tasks.each do |t|
0
+ comment = " # #{t.comment}" if t.comment
0
+ printf "sake %-#{width}s#{comment}\n", t.name
0
+ end
0
+ end
0
+ alias_method :show, :display_tasks_and_comments
0
+
0
+ ##
0
+ # Run Sake even if no Rakefile exists in the current directory.
0
+ alias_method :sake_original_have_rakefile, :have_rakefile
0
+ def have_rakefile(*args)
0
+ @rakefile ||= ''
0
+ sake_original_have_rakefile(*args) || true
0
+ end
0
   end
0
 
0
- def detect_source(args)
0
- args.detect { |arg| arg[/(\w+:\/\/)/] }
0
+ class Task
0
+ ##
0
+ # We want only run a Sake task -- not any other matching
0
+ # or duplicate tasks.
0
+ def enhance(deps=nil, &block)
0
+ @prerequisites |= deps if deps
0
+ @actions = [block] if block_given?
0
+ self
0
+ end
0
   end
0
 end
0
 
0
-Sake.new(ARGV).invoke if $0 == __FILE__
0
+##
0
+# Hacks which give us "Rakefile".is_file?
0
+class String
0
+ def is_file?
0
+ File.exists? self
0
+ end
0
+end
0
+
0
+class Nil
0
+ def is_file?
0
+ false
0
+ end
0
+end
0
+
0
+def die(*message)
0
+ puts message
0
+ exit
0
+end
0
+
0
+Sake.new(ARGV).run if $0 == __FILE__

Comments

    No one has commented yet.