Pit,
Pit said:
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 method `define_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 (c) 2006 Thomas Sawyer
#
# 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 Sawyer
# Author:: Thomas Sawyer
# Copyright:: Copyright (c) 2006 Thomas Sawyer
# 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
# a = @action
# 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(c).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