ratch 0.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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