ratch 0.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/dev/taskable.rb CHANGED
@@ -1,573 +1,120 @@
1
- # TITLE:
2
- #
3
- # Taskable
4
- #
5
- # COPYRIGHT:
6
- #
7
- # Copyright (c) 2006 Thomas Sawyer
8
- #
9
- # LICENSE:
10
- #
11
- # Ruby License
12
- #
13
- # This module is free software. You may use, modify, and/or redistribute this
14
- # software under the same terms as Ruby.
15
- #
16
- # This program is distributed in the hope that it will be useful, but WITHOUT
17
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
- # FOR A PARTICULAR PURPOSE.
19
- #
20
- # AUTHORS:
21
- #
22
- # - Thomas Sawyer
23
- #
24
- # NOTES:
25
- #
26
- # - TODO The included call back does a comparison to Object.
27
- # This is a bit of a hack b/c there is actually no way to
28
- # check if it is the toplevel --a flaw w/ Ruby's toplevel proxy.
29
- #
30
- # - TODO Should Rake's namespace feature be added? This could interfer
31
- # with other definitions of #namespace.
32
- #
33
- # - TODO The only reason the :exec method is defined is b/c instance_exec
34
- # is not in Ruby yet, so until then this is the working hack.
35
- #
36
- # LOG:
37
- #
38
- # - CHANGE 2006-11-14 trans
39
- #
40
- # Taskable has been completely rewritten. While it is essentially
41
- # compatible with the previous implementation, it is not 100% the
42
- # same; mainly in that tasks are not defined as methods any longer.
43
- # This new implementation is now nearly 100% compatible with Rake's
44
- # design. Note, for a basic "taskable" system, more like the old
45
- # version, see depend.rb.
1
+ module Ratch
46
2
 
47
- require 'facets/class_extension'
48
-
49
- $toplevel = self
50
-
51
- # = Taskable
52
- #
53
- # The Taskable module provides a generic task system
54
- # patterned after Rake, but useable in any
55
- # code context --not just with the Rake tool. In other
56
- # words one can create methods with dependencies.
57
- #
58
- # NOTE Unlike methods, tasks can't take independent parameters
59
- # if they are to be used as prerequisites. The arguments passed
60
- # to a task call will also be passed to it's prequisites.
61
- #
62
- # To use Taskable at the toplevel use:
63
- #
64
- # include Taskable
65
- #
66
- # Or if you want all modules to be "taskable":
67
- #
68
- # class Module
69
- # include Taskable
70
- # end
71
-
72
- module Taskable
73
-
74
- def self.included( base )
75
- if base == Object #$toplevel
76
- require 'facets/more/main_as_module.rb'
77
- Module.module_eval{ include TaskableDSL }
78
- else
79
- base.extend TaskableDSL
80
- end
81
- end
82
-
83
- ### CLASS LEVEL ###
84
-
85
- module TaskableDSL
86
-
87
- #--
88
- # TODO Add task namespace functionality ???
89
- #++
90
- #def namespace
3
+ module Taskable
4
+ #def main
5
+ # @main
91
6
  #end
92
7
 
93
- # Define description for subsequent task.
8
+ def tasks
9
+ @tasks ||= {}
10
+ end
94
11
 
95
- def desc(line=nil)
96
- return @_last_description unless line
97
- @_last_description = line.gsub("\n",'')
12
+ def file_tasks
13
+ @file_tasks ||= {}
98
14
  end
99
15
 
100
- # Use up the description for subsequent task.
16
+ # Define a main task.
101
17
 
102
- def desc!
103
- l, @_last_description = @_last_description, nil
104
- l
18
+ def main(name, &block)
19
+ @main = Task.register(name, &block)
20
+ tasks[@main.name] = @main
105
21
  end
106
22
 
107
- # <b>Task</b>
108
- #
109
- #
23
+ # Define a task.
110
24
 
111
- def task( target_to_source, &build )
112
- target, source = *Task.parse(target_to_source)
113
- define_method("#{target}:exec",&build) if build
114
- (@task||={})[target] = Task.new(target, source, desc!, &build)
25
+ def task(name, &block)
26
+ tsk = Task.register(name, &block)
27
+ tasks[tsk.name] = tsk
115
28
  end
116
29
 
117
- # <b>File task</b>
118
- #
119
- # Task must be provide instructions for building the file.
30
+ # Define a file target.
120
31
 
121
- def file( file_to_source, &build )
122
- file, source = *Task.parse(file_to_source)
123
- define_method("#{file}:exec",&build) if build
124
- (@task||={})[file] = FileTask.new(file, source, desc!, &build)
32
+ def file(name, &block)
33
+ #tasks[name.to_s] = FileTask.register(name, &block)
34
+ file_tasks[name.to_s] = FileTask.register(name, &block)
125
35
  end
36
+ end
126
37
 
127
- # <b>Rule task</b>
128
- #
129
- # Task must be provide instructions for building the file(s).
38
+ #
39
+ #
40
+ #
41
+ class Task
130
42
 
131
- def rule( pattern_to_source, &build )
132
- pattern, source = *Task.parse(pattern_to_source)
133
- define_method("#{pattern}:exec",&build) if build
134
- (@task||={})[pattern] = RuleTask.new(pattern, source, desc!, &build)
43
+ def self.tasks
44
+ @@tasks ||= {}
135
45
  end
136
46
 
137
- #
138
-
139
- def instance_tasks( ancestry=true )
140
- @task ||= {}
141
- if ancestry
142
- ancestors.inject(@task.keys) do |m,a|
143
- t = a.instance_variable_get("@task")
144
- m |= t.keys if t
145
- m
146
- end
47
+ def self.register(name_deps, &block)
48
+ if Hash===name_deps
49
+ name = name_deps.keys[0]
50
+ deps = name_deps.values[0]
147
51
  else
148
- @task.keys
52
+ name = name_deps
53
+ deps = []
149
54
  end
55
+ tasks[name.to_s] = new(name, *deps, &block)
150
56
  end
151
57
 
152
- # List of task names with descriptions.
58
+ attr :name
59
+ attr :preqs
60
+ attr :action
153
61
 
154
- def described_tasks( ancestry=true )
155
- memo = []
156
- instance_tasks(ancestry).each do |name|
157
- memo << name if @task[name].desc
158
- end
159
- return memo
62
+ #
63
+ def initialize(name, *preqs, &action)
64
+ @name = name.to_s
65
+ @preqs = preqs
66
+ @action = action
160
67
  end
161
68
 
162
- # List of task names without descriptions.
163
-
164
- def undescribed_tasks( ancestry=true )
165
- memo = []
166
- instance_tasks(ancestry).each do |name|
167
- memo << name unless @task[name].desc
168
- end
169
- return memo
69
+ #
70
+ def call
71
+ plan.each{ |name| @@tasks[name].action_call }
72
+ #action_call
170
73
  end
171
74
 
172
- # Find matching task.
173
- #--
174
- # TODO Maybe this isn't really needed here and can be moved to Task class ???
175
- #++
75
+ #
76
+ def action_call
77
+ @action.call if @action
78
+ end
176
79
 
177
- def instance_task( match )
178
- hit = (@task||={}).values.find do |task|
179
- task.match(match)
80
+ # TODO Check for circular dependencies.
81
+ def plan(list=[])
82
+ if list.include?(name)
83
+ raise "Circular dependency #{name}."
180
84
  end
181
- return hit if hit
182
- ancestors.each do |a|
183
- task_table = a.instance_variable_get("@task")
184
- next unless task_table
185
- hit = task_table.values.find do |task|
186
- task.match(match)
187
- end
188
- break hit if hit
85
+ @preqs.each do |need|
86
+ need = need.to_s
87
+ next if list.include?(need)
88
+ @@tasks[need].plan(list)
189
89
  end
190
- hit
90
+ list << name
91
+ return list
191
92
  end
192
93
 
193
94
  end
194
95
 
195
- ### INSTANCE LEVEL ###
196
-
197
96
  #
198
-
199
- def tasks
200
- (class << self; self; end).instance_tasks
201
- end
202
-
203
- #
204
-
205
- def task(name)
206
- (class << self; self; end).instance_task(name)
207
- end
208
-
209
- # FIXME, THIS STILL WONT WORK AT TOPLEVEL!!!!
210
-
211
- def method_missing(t, *a, &b)
212
- p t
213
- p tasks
214
- p task(t)
215
- #if self.class.respond_to?(:instance_task) && (task = self.class.instance_task(t))
216
- if tsk = task(t)
217
- tsk.run(self, t)
218
- else
219
- super(t.to_sym,*a,&b)
220
- end
221
- end
222
-
223
- end
224
-
225
- #
226
-
227
- class Taskable::Task
228
-
229
- # Parse target => [source,...] argument.
230
-
231
- def self.parse(target_to_source)
232
- if Hash === target_to_source
233
- target = target_to_source.keys[0]
234
- source = target_to_source.values[0]
235
- else
236
- target = target_to_source
237
- source = []
238
- end
239
- return target.to_sym, source.collect{|s| s.to_sym}
240
- end
241
-
242
97
  #
243
-
244
- attr_reader :target, :source, :desc, :build
245
-
246
- alias :name :target
247
- alias :description :desc
248
-
249
- # New task.
250
-
251
- def initialize( target, source, desc=nil, &build )
252
- @target = target
253
- @source = source
254
- @desc = desc
255
- @build = build
256
- end
257
-
258
- # Run task in given context.
259
-
260
- def run( context, target )
261
- task = self
262
- source = @source
263
- build = @build
264
-
265
- presource(context).each do |d|
266
- d.call(context)
267
- end
268
-
269
- call(context)
270
- end
271
-
272
- # Call build exec of task. Note that the use of :exec method
273
- # is due to the lack of #instance_exec which will come wiht Ruby 1.9.
274
-
275
- def call( context )
276
- context.send("#{@target}:exec", self) if @build
277
- end
278
-
279
98
  #
99
+ class FileTask < Task
280
100
 
281
- def match( target )
282
- @target.to_s == target.to_s
283
- end
284
-
285
- # Compile list of all unique prerequisite sources.
286
-
287
- def presource( context, build=[] )
288
- @source.each do |s|
289
- t = context.class.instance_task(s)
290
- raise NoMethodError, 'undefined source' unless t
291
- build.unshift(t)
292
- t.presource(context,build)
293
- end
294
- build.uniq!
295
- build
296
- end
297
-
298
- end
299
-
300
- #
301
-
302
- class Taskable::FileTask < Taskable::Task
303
-
304
- # Run file task in a given context.
305
-
306
- def run( context, target )
307
- task = self
308
- source = @source
309
- build = @build
101
+ def call
102
+ super
310
103
 
311
- context.instance_eval do
312
- needed = false
313
- if File.exist?(file)
314
- #source.each { |s| send(s) if respond_to?(s) }
315
- timestamp = File.mtime(file)
316
- needed = source.any? { |f| File.mtime(f.to_s) > timestamp }
317
- else
318
- timestamp = Time.now - 1
319
- needed = true
320
- end
321
- if needed
322
- build.call(task)
323
- unless File.exist?(file) and File.mtime(file) > timestamp
324
- raise "failed to build -- #{file}"
104
+ files = Dir.glob(@name)
105
+ files.each do |name|
106
+ if File.exist?(name)
107
+ mtime = File.mtime(name)
108
+ needs = @preqs.find do |file|
109
+ !File.exist?(file) || File.mtime(file) > mtime
110
+ end
111
+ else
112
+ needs = true
325
113
  end
114
+ @action.call(@name) if needs
326
115
  end
327
116
  end
117
+
328
118
  end
329
-
330
- #
331
-
332
- def match(target)
333
- @target.to_s == target.to_s
334
- end
335
-
336
- end
337
-
338
- #
339
-
340
- class Taskable::RuleTask < Taskable::FileTask
341
-
342
- # Run rule task in given context.
343
-
344
- def run( context, target )
345
- @target = target
346
- super(context)
347
- end
348
-
349
- #
350
-
351
- def match(target)
352
- case @target
353
- when Regexp
354
- @target =~ target.to_s
355
- when String
356
- #if @target.index('*') #TODO
357
- # /#{@target.gsub('*', '.*?')}/ =~ target
358
- if @target.index('.') == 0
359
- /#{Regexp.escape(@target)}$/ =~ target
360
- else
361
- super
362
- end
363
- else
364
- super
365
- end
366
- end
367
-
368
119
  end
369
120
 
370
-
371
-
372
- # _____ _
373
- # |_ _|__ ___| |_
374
- # | |/ _ \/ __| __|
375
- # | | __/\__ \ |_
376
- # |_|\___||___/\__|
377
- #
378
- =begin ##test
379
-
380
- require 'test/unit'
381
-
382
- class TestTaskable1 < Test::Unit::TestCase
383
-
384
- module M
385
- include Taskable
386
- task :m1 => [ :c1 ] do @x << "m1" end
387
- task :m2 => [ :c2 ] do @x << "m2" end
388
- task :m3 => [ :c3 ] do @x << "m3" end
389
- task :m4 => [ :c1, :c2, :c3 ] do @x << "m4" end
390
- task :m5 do @x << 'm5' end
391
- end
392
-
393
- class B
394
- include Taskable
395
- attr :x
396
- def initialize ; @x = [] ; end
397
-
398
- desc "test-b1"
399
- task :b1 do @x << "b1" end
400
- task :b2 => [ :b1 ]
401
- end
402
-
403
- class C
404
- include M
405
- attr :x
406
- def initialize ; @x = [] ; end
407
-
408
- task :c1 do @x << "c1" end
409
- task :c2 => [ :c1, :c1 ] do @x << "c2" end
410
- task :c3 => [ :c1, :c2 ] do @x << "c3" end
411
- task :c4 => [ :m1 ] do @x << "c4" end
412
- task :c5 => [ :c5 ] do @x << "c5" end
413
- task :c6 => [ :c7 ] do @x << "c6" end
414
- task :c7 => [ :c6 ] do @x << "c7" end
415
- end
416
-
417
- class D < C
418
- task :d1 => [ :c1 ] do @x << "d1" ; end
419
- task :d2 => [ :m1 ] do @x << "d2" ; end
420
- end
421
-
422
- module N
423
- include M
424
- end
425
-
426
- class E
427
- include N
428
- attr :x
429
- def initialize ; @x = [] ; end
430
-
431
- task :e1 => [ :c1 ] do @x << "e1" ; end
432
- task :e2 => [ :m1 ] do @x << "e2" ; end
433
- task :e3 => [ :m5 ] do @x << "e3" ; end
434
- end
435
-
436
- module O
437
- include Taskable
438
- attr :x
439
- task :o1 do (@x||=[]) << "o1" end
440
- task :o2 => [ :o1 ] do (@x||=[]) << "o2" end
441
- end
442
-
443
- # tests
444
-
445
- def test_001
446
- assert( B.described_tasks.include?(:b1) )
447
- end
448
-
449
- def test_B1
450
- b = B.new ; b.b1
451
- assert_equal( [ 'b1' ], b.x )
452
- end
453
-
454
- def test_B2
455
- b = B.new ; b.b2
456
- assert_equal( [ 'b1' ], b.x )
457
- end
458
-
459
- def test_C1
460
- c = C.new ; c.c1
461
- assert_equal( [ 'c1' ], c.x )
462
- end
463
-
464
- def test_C2
465
- c = C.new ; c.c2
466
- assert_equal( [ 'c1', 'c2' ], c.x )
467
- end
468
-
469
- def test_C3
470
- c = C.new ; c.c3
471
- assert_equal( [ 'c1', 'c2', 'c3' ], c.x )
472
- end
473
-
474
- def test_C4
475
- c = C.new ; c.c4
476
- assert_equal( [ 'c1', 'm1', 'c4' ], c.x )
477
- end
478
-
479
- def test_M1
480
- c = C.new ; c.m1
481
- assert_equal( [ 'c1', 'm1' ], c.x )
482
- end
483
-
484
- def test_M2
485
- c = C.new ; c.m2
486
- assert_equal( [ 'c1', 'c2', 'm2' ], c.x )
487
- end
488
-
489
- def test_M3
490
- c = C.new ; c.m3
491
- assert_equal( [ 'c1', 'c2', 'c3', 'm3' ], c.x )
492
- end
493
-
494
- def test_M4
495
- c = C.new ; c.m4
496
- assert_equal( [ 'c1', 'c2', 'c3', 'm4' ], c.x )
497
- end
498
-
499
- def test_D1
500
- d = D.new ; d.d1
501
- assert_equal( [ 'c1', 'd1' ], d.x )
502
- end
503
-
504
- def test_D2
505
- d = D.new ; d.d2
506
- assert_equal( [ 'c1', 'm1', 'd2' ], d.x )
507
- end
508
-
509
- def test_E1
510
- e = E.new
511
- assert_raises( NoMethodError ) { e.e1 }
512
- #assert_equal( [ 'c1', 'e1' ], e.x )
513
- end
514
-
515
- def test_E2
516
- e = E.new
517
- assert_raises( NoMethodError ) { e.e2 }
518
- #assert_equal( [ 'c1', 'm1', 'e2' ], e.x )
519
- end
520
-
521
- def test_E3
522
- e = E.new ; e.e3
523
- assert_equal( [ 'm5', 'e3' ], e.x )
524
- end
525
-
526
- # def test_F1
527
- # F.o1
528
- # assert_equal( [ 'o1' ], F.x )
529
- # end
530
- #
531
- # def test_F2
532
- # F.o2
533
- # assert_equal( [ 'o1', 'o1', 'o2' ], F.x )
534
- # end
535
-
536
- end
537
-
538
- =end
539
-
540
- ##
541
- # Test toplevel usage.
542
- #
543
-
544
- include Taskable
545
-
546
- p Object.ancestors
547
-
548
- task :foo do
549
- "foo"
550
- end
551
-
552
- task :bar => [ :foo ] do
553
- "bar"
554
- end
555
-
556
- #class TestTaskable2 #< Test::Unit::TestCase
557
- def test_01
558
- puts foo
559
- end
560
-
561
- def test_02
562
- puts bar
563
- end
564
- #end
565
-
566
- test_01
567
- test_02
568
-
569
- #=end
570
-
571
- # Author:: Thomas Sawyer
572
- # Copyright:: Copyright (c) 2006 Thomas Sawyer
573
- # License:: Ruby License