lib/middleman-core/vendor/hooks-0.2.0/lib/hooks.rb



require File.join(File.dirname(__FILE__), "hooks/inheritable_attribute")

# Almost like ActiveSupport::Callbacks but 76,6% less complex.
#
# Example:
#
#   class CatWidget < Apotomo::Widget
#     define_hook :after_dinner
#
# Now you can add callbacks to your hook declaratively in your class.
#
#     after_dinner do puts "Ice cream!" end
#     after_dinner :have_a_desert   # => refers to CatWidget#have_a_desert
# 
# Running the callbacks happens on instances. It will run the block and #have_a_desert from above.
#
#   cat.run_hook :after_dinner
module Hooks
  VERSION = "0.2.0"
  
  def self.included(base)
    base.extend InheritableAttribute
    base.extend ClassMethods
  end
  
  module ClassMethods
    def define_hook(name)
      accessor_name = "_#{name}_callbacks"
      
      setup_hook_accessors(accessor_name)
      define_hook_writer(name, accessor_name)
    end
    
    # Like Hooks#run_hook but for the class. Note that +:callbacks+ must be class methods.
    #
    # Example:
    #
    # class Cat
    #   after_eight :grab_a_beer
    #   
    #   def self.grab_a_beer(*) # and so on...
    # 
    # where <tt>Cat.run_hook :after_eight</tt> will call the class method +grab_a_beer+.
    def run_hook(name, *args)
      run_hook_for(name, self, *args)
    end    
    
    def run_hook_for(name, scope, *args)
      callbacks_for_hook(name).each do |callback|
        if callback.kind_of? Symbol
          scope.send(callback, *args)
        else
          scope.instance_exec(*args, &callback)
        end 
      end
    end
    
    # Returns the callbacks for +name+. Handy if you want to run the callbacks yourself, say when
    # they should be executed in another context.
    #
    # Example:
    #
    #   def initialize
    #     self.class.callbacks_for_hook(:after_eight).each do |callback|
    #       instance_exec(self, &callback)
    #     end
    #
    # would run callbacks in the object _instance_ context, passing +self+ as block parameter.
    def callbacks_for_hook(name)
      send("_#{name}_callbacks")
    end
    
  private
    
    def define_hook_writer(hook, accessor_name)
      self.send(:define_method, hook.to_sym) do |&block|
        if self.class.respond_to?(hook)
          self.class.send(hook.to_sym, &block)
        end
      end
    
      instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
        def #{hook}(method=nil, &block)
          #{accessor_name} << (block || method)
        end
      RUBY_EVAL
    end
    
    def setup_hook_accessors(accessor_name)
      inheritable_attr(accessor_name)
      send("#{accessor_name}=", [])  # initialize ivar.
    end  
  end
  
  # Runs the callbacks (method/block) for the specified hook +name+. Additional arguments will 
  # be passed to the callback.
  #
  # Example:
  #
  #   cat.run_hook :after_dinner, "i want ice cream!"
  #
  # will invoke the callbacks like
  # 
  #   desert("i want ice cream!")
  #   block.call("i want ice cream!")
  def run_hook(name, *args)
    self.class.run_hook_for(name, self, *args)
  end
end