Pit,
Pit C. wrote:
Trans schrieb:
Unfortuately when I use this to make a Rake command-line emulator I
want to use the module at the toplevel and can’t.
Tom, I think I still don’t get what your problem is. If you want to use
a module at the toplevel just include it in Object.
The module is for module/class level. It uses #define_method and
#ancestors. Including it into Object just gives:
./taskable.rb:184:in task': undefined methoddefine_method’ for
main:Object (NoMethodError)
Using extend makes the methods not available at the toplevel.
I pasted the code base I’m working on below. To try it, create a task
and call it:
task :foo do
puts “foo”
end
foo
T.
= taskable.rb
== Copyright © 2006 Thomas S.
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.
== Auhtor(s)
* Thomas S.
Author:: Thomas S.
Copyright:: Copyright © 2006 Thomas S.
License:: Ruby License
require ‘tsort’
= TSort
TSort is used to determine which prerequisites to run
for a task.
Here we add a convenience method --if such a long method
name can be said to be convenient
NOTE: Although I ended
up not using this after all --it still may proved useful and
may eventually be added to Facets or submitted as a patch.
module TSort
def strongly_connected_components_from(node)
result = []
each_strongly_connected_component_from(node) do |component|
result << component
end
result
end
end
= Task
A Task is a Method with prerequisite dependencies.
NOTE Can’t subclass Method b/c Method has no initiator.
Not sure that would be a good idea anyway.
class Task
attr_accessor :description
def initialize( name, container, preq, desc=nil, &action )
@name = name
@container = container
@prerequisite = preq || []
@description = desc
@action = action
end
def prerequisite
@prerequisite ||= []
end
#–
# TODO Problem is ‘a’ can’t take any parameters,
# and there’s no way to sort the tasks
# from here. FIX if possible.
#++
def to_proc
r = @prerequisite
n = @name
lambda {
r.each{ |d| send(d) unless n == d }
instance_eval &a if a
}
end
def complete
# ensure no cycles in the graph and extract call graph
#own = (class << self; self; end) # in case there are singletons?
t = Graph.new( container, name )
t.each_strongly_connected_component_from( name ) do |c|
#@container.instance_task©.call
@container.send("->#{d}")
end
end
def call
@action.call
end
def each(&blk)
@prerequisite.each(&blk)
end
Task::Graph is a Tarjan-sorted hash.
It is used to generate the proper dependency chains.
TODO Make Multiton off of base?
class Graph < Hash
include TSort
def initialize( base, name )
@base = base.class
@name = name
instance_map( name )
end
def instance_map( name )
t = @base.instance_task(name)
raise "task does not exist -- #{name} in #{@base}" unless t
self[name] = t.prerequisite || []
self[name].each { |r| instance_map( r ) }
end
alias_method :tsort_each_node, :each_key
def tsort_each_child(node, &block)
fetch(node).each(&block)
end
end
end
= 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 parameters
if they are to be used as prerequisites. They can
pass on the parameters of the original call though.
module Taskable
Define description for subsequent task.
def desc(line=nil)
return @last_description unless line
@last_description = line.gsub("\n",’’)
end
Use up the description for subsequent task.
def desc!
l = @last_description
@last_description = nil
l
end
Define a task.
def task(args, &action)
if Hash === args
raise ArgumentError, “#{args.size} for 1” if args.size != 1
name, *deps = *(args.to_a.flatten)
name = name.to_sym
deps = deps.compact.collect{ |e| e.to_sym }
else
name, deps = args.to_sym, []
end
# create task by creating core and callable methods.
# We do it this way b/c otherwise we’d have to use
# instance_eval and then can’t pass paramaters (TODO fix w/Ruby
1.9).
if action
define_method( “->#{name}”, &action ) # core of the task
define_method( name ) do |*args|
# ensure no cycles in the graph and extract call graph
#own = (class << self; self; end) # in case there are
singletons?
todolist = Task::Graph.new( self, name )
todolist.each_strongly_connected_component_from( name ) do |c|
send("->#{c}", *args)
end
end
instance_task_table[name] = Task.new( name, self, deps, desc!,
&action )
elsif m = instance_task(name)
#m.description = desc! # TODO should this apply only if there is
an action?
m.prerequisite.concat deps unless deps.empty?
m
else
#define_method( “->#{name}”, lambda{} ) # empty action
define_method( name ) do |*args|
# ensure no cycles in the graph and extract call graph
#own = (class << self; self; end) # in case there are
singletons?
todolist = Task::Graph.new( self, name )
todolist.each_strongly_connected_component_from( name ) do |c|
send("->#{c}", *args) unless “#{c}” == “#{name}”
end
end
instance_task_table[name] = Task.new( name, self, deps, desc!,
&action )
end
end
Access a task.
def instance_task( name )
name = name.to_sym
if instance_task_table and r = instance_task_table[name]
return r
end
ancestors.each do |anc|
itt = anc.instance_task_table
#instance_variable_get("@instance_task_table")
if itt and r = itt[name]
return r
end
end
nil
end
List of task names.
def instance_tasks( include_ancestors=true )
if include_ancestors and ancestors and ancestors[1]
instance_task_table.keys | ancestors[1].instance_tasks
else
instance_task_table.keys
end
end
protected
def instance_task_table
@instance_task_table ||= {}
end
end
class Module
include Taskable
end
include Taskable