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