dev/taskable.rb in ratch-0.1 vs dev/taskable.rb in ratch-0.2.1
- old
+ new
@@ -1,573 +1,120 @@
-# TITLE:
-#
-# Taskable
-#
-# COPYRIGHT:
-#
-# Copyright (c) 2006 Thomas Sawyer
-#
-# LICENSE:
-#
-# Ruby License
-#
-# This module is free software. You may use, modify, and/or redistribute this
-# software under the same terms as Ruby.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-# AUTHORS:
-#
-# - Thomas Sawyer
-#
-# NOTES:
-#
-# - TODO The included call back does a comparison to Object.
-# This is a bit of a hack b/c there is actually no way to
-# check if it is the toplevel --a flaw w/ Ruby's toplevel proxy.
-#
-# - TODO Should Rake's namespace feature be added? This could interfer
-# with other definitions of #namespace.
-#
-# - TODO The only reason the :exec method is defined is b/c instance_exec
-# is not in Ruby yet, so until then this is the working hack.
-#
-# LOG:
-#
-# - CHANGE 2006-11-14 trans
-#
-# Taskable has been completely rewritten. While it is essentially
-# compatible with the previous implementation, it is not 100% the
-# same; mainly in that tasks are not defined as methods any longer.
-# This new implementation is now nearly 100% compatible with Rake's
-# design. Note, for a basic "taskable" system, more like the old
-# version, see depend.rb.
+module Ratch
-require 'facets/class_extension'
+ module Taskable
+ #def main
+ # @main
+ #end
-$toplevel = self
-
-# = Taskable
-#
-# The Taskable module provides a generic task system
-# patterned after Rake, but useable in any
-# code context --not just with the Rake tool. In other
-# words one can create methods with dependencies.
-#
-# NOTE Unlike methods, tasks can't take independent parameters
-# if they are to be used as prerequisites. The arguments passed
-# to a task call will also be passed to it's prequisites.
-#
-# To use Taskable at the toplevel use:
-#
-# include Taskable
-#
-# Or if you want all modules to be "taskable":
-#
-# class Module
-# include Taskable
-# end
-
-module Taskable
-
- def self.included( base )
- if base == Object #$toplevel
- require 'facets/more/main_as_module.rb'
- Module.module_eval{ include TaskableDSL }
- else
- base.extend TaskableDSL
+ def tasks
+ @tasks ||= {}
end
- end
- ### CLASS LEVEL ###
-
- module TaskableDSL
-
- #--
- # TODO Add task namespace functionality ???
- #++
- #def namespace
- #end
-
- # Define description for subsequent task.
-
- def desc(line=nil)
- return @_last_description unless line
- @_last_description = line.gsub("\n",'')
+ def file_tasks
+ @file_tasks ||= {}
end
- # Use up the description for subsequent task.
+ # Define a main task.
- def desc!
- l, @_last_description = @_last_description, nil
- l
+ def main(name, &block)
+ @main = Task.register(name, &block)
+ tasks[@main.name] = @main
end
- # <b>Task</b>
- #
- #
+ # Define a task.
- def task( target_to_source, &build )
- target, source = *Task.parse(target_to_source)
- define_method("#{target}:exec",&build) if build
- (@task||={})[target] = Task.new(target, source, desc!, &build)
+ def task(name, &block)
+ tsk = Task.register(name, &block)
+ tasks[tsk.name] = tsk
end
- # <b>File task</b>
- #
- # Task must be provide instructions for building the file.
+ # Define a file target.
- def file( file_to_source, &build )
- file, source = *Task.parse(file_to_source)
- define_method("#{file}:exec",&build) if build
- (@task||={})[file] = FileTask.new(file, source, desc!, &build)
+ def file(name, &block)
+ #tasks[name.to_s] = FileTask.register(name, &block)
+ file_tasks[name.to_s] = FileTask.register(name, &block)
end
+ end
- # <b>Rule task</b>
- #
- # Task must be provide instructions for building the file(s).
+ #
+ #
+ #
+ class Task
- def rule( pattern_to_source, &build )
- pattern, source = *Task.parse(pattern_to_source)
- define_method("#{pattern}:exec",&build) if build
- (@task||={})[pattern] = RuleTask.new(pattern, source, desc!, &build)
+ def self.tasks
+ @@tasks ||= {}
end
- #
-
- def instance_tasks( ancestry=true )
- @task ||= {}
- if ancestry
- ancestors.inject(@task.keys) do |m,a|
- t = a.instance_variable_get("@task")
- m |= t.keys if t
- m
- end
+ def self.register(name_deps, &block)
+ if Hash===name_deps
+ name = name_deps.keys[0]
+ deps = name_deps.values[0]
else
- @task.keys
+ name = name_deps
+ deps = []
end
+ tasks[name.to_s] = new(name, *deps, &block)
end
- # List of task names with descriptions.
+ attr :name
+ attr :preqs
+ attr :action
- def described_tasks( ancestry=true )
- memo = []
- instance_tasks(ancestry).each do |name|
- memo << name if @task[name].desc
- end
- return memo
+ #
+ def initialize(name, *preqs, &action)
+ @name = name.to_s
+ @preqs = preqs
+ @action = action
end
- # List of task names without descriptions.
+ #
+ def call
+ plan.each{ |name| @@tasks[name].action_call }
+ #action_call
+ end
- def undescribed_tasks( ancestry=true )
- memo = []
- instance_tasks(ancestry).each do |name|
- memo << name unless @task[name].desc
- end
- return memo
+ #
+ def action_call
+ @action.call if @action
end
- # Find matching task.
- #--
- # TODO Maybe this isn't really needed here and can be moved to Task class ???
- #++
-
- def instance_task( match )
- hit = (@task||={}).values.find do |task|
- task.match(match)
+ # TODO Check for circular dependencies.
+ def plan(list=[])
+ if list.include?(name)
+ raise "Circular dependency #{name}."
end
- return hit if hit
- ancestors.each do |a|
- task_table = a.instance_variable_get("@task")
- next unless task_table
- hit = task_table.values.find do |task|
- task.match(match)
- end
- break hit if hit
+ @preqs.each do |need|
+ need = need.to_s
+ next if list.include?(need)
+ @@tasks[need].plan(list)
end
- hit
+ list << name
+ return list
end
end
- ### INSTANCE LEVEL ###
-
#
-
- def tasks
- (class << self; self; end).instance_tasks
- end
-
#
-
- def task(name)
- (class << self; self; end).instance_task(name)
- end
-
- # FIXME, THIS STILL WONT WORK AT TOPLEVEL!!!!
-
- def method_missing(t, *a, &b)
-p t
-p tasks
-p task(t)
- #if self.class.respond_to?(:instance_task) && (task = self.class.instance_task(t))
- if tsk = task(t)
- tsk.run(self, t)
- else
- super(t.to_sym,*a,&b)
- end
- end
-
-end
-
-#
-
-class Taskable::Task
-
- # Parse target => [source,...] argument.
-
- def self.parse(target_to_source)
- if Hash === target_to_source
- target = target_to_source.keys[0]
- source = target_to_source.values[0]
- else
- target = target_to_source
- source = []
- end
- return target.to_sym, source.collect{|s| s.to_sym}
- end
-
#
+ class FileTask < Task
- attr_reader :target, :source, :desc, :build
+ def call
+ super
- alias :name :target
- alias :description :desc
-
- # New task.
-
- def initialize( target, source, desc=nil, &build )
- @target = target
- @source = source
- @desc = desc
- @build = build
- end
-
- # Run task in given context.
-
- def run( context, target )
- task = self
- source = @source
- build = @build
-
- presource(context).each do |d|
- d.call(context)
- end
-
- call(context)
- end
-
- # Call build exec of task. Note that the use of :exec method
- # is due to the lack of #instance_exec which will come wiht Ruby 1.9.
-
- def call( context )
- context.send("#{@target}:exec", self) if @build
- end
-
- #
-
- def match( target )
- @target.to_s == target.to_s
- end
-
- # Compile list of all unique prerequisite sources.
-
- def presource( context, build=[] )
- @source.each do |s|
- t = context.class.instance_task(s)
- raise NoMethodError, 'undefined source' unless t
- build.unshift(t)
- t.presource(context,build)
- end
- build.uniq!
- build
- end
-
-end
-
-#
-
-class Taskable::FileTask < Taskable::Task
-
- # Run file task in a given context.
-
- def run( context, target )
- task = self
- source = @source
- build = @build
-
- context.instance_eval do
- needed = false
- if File.exist?(file)
- #source.each { |s| send(s) if respond_to?(s) }
- timestamp = File.mtime(file)
- needed = source.any? { |f| File.mtime(f.to_s) > timestamp }
- else
- timestamp = Time.now - 1
- needed = true
- end
- if needed
- build.call(task)
- unless File.exist?(file) and File.mtime(file) > timestamp
- raise "failed to build -- #{file}"
+ files = Dir.glob(@name)
+ files.each do |name|
+ if File.exist?(name)
+ mtime = File.mtime(name)
+ needs = @preqs.find do |file|
+ !File.exist?(file) || File.mtime(file) > mtime
+ end
+ else
+ needs = true
end
+ @action.call(@name) if needs
end
end
+
end
-
- #
-
- def match(target)
- @target.to_s == target.to_s
- end
-
end
-#
-
-class Taskable::RuleTask < Taskable::FileTask
-
- # Run rule task in given context.
-
- def run( context, target )
- @target = target
- super(context)
- end
-
- #
-
- def match(target)
- case @target
- when Regexp
- @target =~ target.to_s
- when String
- #if @target.index('*') #TODO
- # /#{@target.gsub('*', '.*?')}/ =~ target
- if @target.index('.') == 0
- /#{Regexp.escape(@target)}$/ =~ target
- else
- super
- end
- else
- super
- end
- end
-
-end
-
-
-
-# _____ _
-# |_ _|__ ___| |_
-# | |/ _ \/ __| __|
-# | | __/\__ \ |_
-# |_|\___||___/\__|
-#
-=begin ##test
-
- require 'test/unit'
-
- class TestTaskable1 < Test::Unit::TestCase
-
- module M
- include Taskable
- task :m1 => [ :c1 ] do @x << "m1" end
- task :m2 => [ :c2 ] do @x << "m2" end
- task :m3 => [ :c3 ] do @x << "m3" end
- task :m4 => [ :c1, :c2, :c3 ] do @x << "m4" end
- task :m5 do @x << 'm5' end
- end
-
- class B
- include Taskable
- attr :x
- def initialize ; @x = [] ; end
-
- desc "test-b1"
- task :b1 do @x << "b1" end
- task :b2 => [ :b1 ]
- end
-
- class C
- include M
- attr :x
- def initialize ; @x = [] ; end
-
- task :c1 do @x << "c1" end
- task :c2 => [ :c1, :c1 ] do @x << "c2" end
- task :c3 => [ :c1, :c2 ] do @x << "c3" end
- task :c4 => [ :m1 ] do @x << "c4" end
- task :c5 => [ :c5 ] do @x << "c5" end
- task :c6 => [ :c7 ] do @x << "c6" end
- task :c7 => [ :c6 ] do @x << "c7" end
- end
-
- class D < C
- task :d1 => [ :c1 ] do @x << "d1" ; end
- task :d2 => [ :m1 ] do @x << "d2" ; end
- end
-
- module N
- include M
- end
-
- class E
- include N
- attr :x
- def initialize ; @x = [] ; end
-
- task :e1 => [ :c1 ] do @x << "e1" ; end
- task :e2 => [ :m1 ] do @x << "e2" ; end
- task :e3 => [ :m5 ] do @x << "e3" ; end
- end
-
- module O
- include Taskable
- attr :x
- task :o1 do (@x||=[]) << "o1" end
- task :o2 => [ :o1 ] do (@x||=[]) << "o2" end
- end
-
- # tests
-
- def test_001
- assert( B.described_tasks.include?(:b1) )
- end
-
- def test_B1
- b = B.new ; b.b1
- assert_equal( [ 'b1' ], b.x )
- end
-
- def test_B2
- b = B.new ; b.b2
- assert_equal( [ 'b1' ], b.x )
- end
-
- def test_C1
- c = C.new ; c.c1
- assert_equal( [ 'c1' ], c.x )
- end
-
- def test_C2
- c = C.new ; c.c2
- assert_equal( [ 'c1', 'c2' ], c.x )
- end
-
- def test_C3
- c = C.new ; c.c3
- assert_equal( [ 'c1', 'c2', 'c3' ], c.x )
- end
-
- def test_C4
- c = C.new ; c.c4
- assert_equal( [ 'c1', 'm1', 'c4' ], c.x )
- end
-
- def test_M1
- c = C.new ; c.m1
- assert_equal( [ 'c1', 'm1' ], c.x )
- end
-
- def test_M2
- c = C.new ; c.m2
- assert_equal( [ 'c1', 'c2', 'm2' ], c.x )
- end
-
- def test_M3
- c = C.new ; c.m3
- assert_equal( [ 'c1', 'c2', 'c3', 'm3' ], c.x )
- end
-
- def test_M4
- c = C.new ; c.m4
- assert_equal( [ 'c1', 'c2', 'c3', 'm4' ], c.x )
- end
-
- def test_D1
- d = D.new ; d.d1
- assert_equal( [ 'c1', 'd1' ], d.x )
- end
-
- def test_D2
- d = D.new ; d.d2
- assert_equal( [ 'c1', 'm1', 'd2' ], d.x )
- end
-
- def test_E1
- e = E.new
- assert_raises( NoMethodError ) { e.e1 }
- #assert_equal( [ 'c1', 'e1' ], e.x )
- end
-
- def test_E2
- e = E.new
- assert_raises( NoMethodError ) { e.e2 }
- #assert_equal( [ 'c1', 'm1', 'e2' ], e.x )
- end
-
- def test_E3
- e = E.new ; e.e3
- assert_equal( [ 'm5', 'e3' ], e.x )
- end
-
- # def test_F1
- # F.o1
- # assert_equal( [ 'o1' ], F.x )
- # end
- #
- # def test_F2
- # F.o2
- # assert_equal( [ 'o1', 'o1', 'o2' ], F.x )
- # end
-
- end
-
-=end
-
- ##
- # Test toplevel usage.
- #
-
- include Taskable
-
-p Object.ancestors
-
- task :foo do
- "foo"
- end
-
- task :bar => [ :foo ] do
- "bar"
- end
-
- #class TestTaskable2 #< Test::Unit::TestCase
- def test_01
- puts foo
- end
-
- def test_02
- puts bar
- end
- #end
-
- test_01
- test_02
-
-#=end
-
-# Author:: Thomas Sawyer
-# Copyright:: Copyright (c) 2006 Thomas Sawyer
-# License:: Ruby License