From 7916139726ab7268e907c91f7d04a8c5b9f99bcb Mon Sep 17 00:00:00 2001 From: monocle Date: Sun, 10 Apr 2011 16:08:43 -0700 Subject: [PATCH] Added hook/callback feature. --- lib/guard/hook.rb | 61 ++++++++++++++++++++++++++++++ spec/guard/hook_spec.rb | 82 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 lib/guard/hook.rb create mode 100644 spec/guard/hook_spec.rb diff --git a/lib/guard/hook.rb b/lib/guard/hook.rb new file mode 100644 index 0000000..0fadc12 --- /dev/null +++ b/lib/guard/hook.rb @@ -0,0 +1,61 @@ +module Guard + module Hook + def self.included(base) + base.send :include, InstanceMethods + end + + module InstanceMethods + + # When passed a sybmol, #hook will generate a hook name + # from the symbol and calling method name. When passed + # a string, #hook will turn the string into a symbol + # directly. + def hook(event) + if event.class == Symbol + calling_method = caller[0][/`([^']*)'/, 1] + hook_name = "#{calling_method}_#{event}".to_sym + else + hook_name = event.to_sym + end + + UI.info "\nHook :#{hook_name} executed for #{self.class}" + Hook.notify(self.class, hook_name) + end + end + + class << self + def callbacks + @callbacks ||= Hash.new { |hash, key| hash[key] = [] } + end + + def add(listener, guard_class, events) + _events = events.class == Array ? events : [events] + _events.each do |event| + callbacks[[guard_class, event]] << listener + end + end + + def has_callback?(listener, guard_class, event) + listeners(guard_class, event).include? listener + end + + def notify(guard_class, event) + callbacks[[guard_class, event]].each do |listener| + listener.call(guard_class, event) + end + end + + def reset! + @callbacks = nil + end + + private + + def listeners(guard_class, event) + callbacks[[guard_class, event]].inject([]) do |all, arr| + all << arr + end + end + end + end +end diff --git a/spec/guard/hook_spec.rb b/spec/guard/hook_spec.rb new file mode 100644 index 0000000..f9b86ae --- /dev/null +++ b/spec/guard/hook_spec.rb @@ -0,0 +1,82 @@ +require "spec_helper" +require 'guard/guard' +require "guard/hook" + +describe Guard::Hook do + class Guard::Dummy < Guard::Guard + include Guard::Hook + + def run_all + hook :begin + hook :end + end + end + + let(:guard_class) { ::Guard::Dummy } + let(:listener) { double('listener').as_null_object } + + context "--module methods--" do + before { subject.add(listener, guard_class, :start_begin) } + + after { subject.reset! } + + describe ".add " do + it "can add a single callback" do + subject.has_callback?(listener, guard_class, :start_begin).should be_true + end + + it "can add multiple callbacks" do + subject.add(listener, guard_class, [:event1, :event2]) + subject.has_callback?(listener, guard_class, :event1).should be_true + subject.has_callback?(listener, guard_class, :event2).should be_true + end + end + + describe ".notify " do + it "sends :call to the given Guard class's callbacks" do + listener.should_receive(:call).with(guard_class, :start_begin) + subject.notify(guard_class, :start_begin) + end + + it "doesn't run callbacks of different types" do + listener2 = double('listener2') + subject.add(listener2, guard_class, :start_end) + listener2.should_not_receive(:call).with(guard_class, :start_end) + subject.notify(guard_class, :start_begin) + end + + it "doesn't run callbacks of the wrong class" do + guard2_class = double('Guard::Dummy2').class + subject.add(listener, guard2_class, :start_begin) + listener.should_not_receive(:call).with(guard2_class, :start_begin) + subject.notify(guard_class, :start_begin) + end + end + end + + describe "#hook " do + it "calls Guard::Hook.notify" do + guard = guard_class.new + Guard::Hook.should_receive(:notify). + with(guard_class, :run_all_begin) + + Guard::Hook.should_receive(:notify). + with(guard_class, :run_all_end) + + guard.run_all + end + + it "if passed a string parameter, will use that for the hook name" do + guard_class.class_eval do + def start + hook "my_hook" + end + end + + guard = guard_class.new + Guard::Hook.should_receive(:notify). + with(guard_class, :my_hook) + guard.start + end + end +end