module Chrysalis
# Loader is responsible for loading rakefiles.
#
# The loader loads the rakefiles defined by dependencies. The tasks defined
# within a dependency rakefile are intercepted and re-synthesized as
# all:* tasks.
#
# Additionally, a hook is declared in order to receive notification that the
# main rakefile has been loaded. The tasks in the main rakefile serve to seed
# the all: namespace with tasks, prior to retrieving any
# dependencies.
class Loader
include Singleton
def initialize
@loading = :main
@icept_scope = []
@icept_tasks = []
@last_comment = nil
Chrysalis.hook :rakefile_loaded, self.method(:on_rakefile_loaded).to_proc
end
# Returns the URL of the project currently being loaded.
def loading
@loading
end
# Loads a dependency's rakefile.
#
# The +url+ is the location of the repository from which the dependency was
# retrieved. The +working_copy+ is the WorkingCopy retrieved.
#
# This function will search for and load the rakefile found in the working
# copy's path, if one exists.
def load(url, working_copy)
@loading = url
Rake::Application::DEFAULT_RAKEFILES.each do |file|
path = Pathname.new(working_copy.path).join(file)
if File.exist?(path)
Kernel.load(path, true)
return
end
end
ensure
# If the manifest does not contain an entry for the URL being loaded, one
# of two things occured:
# 1. a project was not instantiated in the rakefile -- or --
# 2. no rakefile existed in the working copy's path
#
# In either case, an empty project will be created as a placeholder.
if Chrysalis::Manifest.instance[@loading].nil?
proj = Project.new
end
Chrysalis::Manifest.instance[@loading].working_copy = working_copy
Chrysalis.post :rakefile_loaded
end
def on_rakefile_loaded # :nodoc:
return if Chrysalis::Manifest.instance[@loading].nil?
@icept_tasks.each do |task|
Chrysalis::Manifest.instance[@loading].task(task[0], task[1])
Chrysalis::TaskManager.synthesize_task(task[0], task[1])
end
ensure
@loading = :main
@icept_tasks = []
end
def task_intercept(name)
scoped_name = (@icept_scope + [name]).join(':')
@icept_tasks << [scoped_name, @last_comment]
@last_comment = nil
yield if @loading == :main
end
def file_task_intercept(name)
@icept_tasks << [name, @last_comment]
@last_comment = nil
yield if @loading == :main
end
def desc_intercept(comment)
@last_comment = comment
yield if @loading == :main
end
#--
# TODO: If name is nil, Rake generates an anonymous namespace. Chrysalis
# currently does not support this feature. In practice, anonymous
# namespaces are rarely used. However, support should be implemented
# for maximum compatibility.
def namespace_intercept(name, orig_block)
@icept_scope.push(name)
# Call the original block given to the namespace. This allows is to
# progress deeper, intercepting further tasks within the namespace.
#
# TODO: Rake instantiates a NameSpace, which is yeiled as a parameter.
# In practice, most code doesn't expect this parameter. However,
# support for it is currently unimplemented, but should be
# implemented for maximum compatibility.
#
# Example:
# orig_block.call(a_namespace)
if @loading != :main
orig_block.call
else
yield
end
ensure
@icept_scope.pop
end
def rule_intercept
yield if @loading == :main
end
end
module Hookable
@@handlers = []
def hook(evt, proc)
@@handlers << [evt, proc]
end
def post(evt, *args)
callbacks = @@handlers.select { |e, ignore| evt == e }
callbacks.each do |ignore, proc|
proc.call(*args)
end
end
end
class << self
include Hookable
end
end