Skip to content

Commit

Permalink
Added #callback DSL, modified Guard and Guard::Hook a bit in conseque…
Browse files Browse the repository at this point in the history
…nce.

Signed-off-by: Rémy Coutable <[email protected]>
  • Loading branch information
rymai committed Apr 16, 2011
1 parent d47aebd commit 44aed32
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 64 deletions.
10 changes: 6 additions & 4 deletions lib/guard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ def run_on_change_for_all_guards(files)
# Let a guard execute its task but
# fire it if his work leads to a system failure
def supervised_task(guard, task_to_supervise, *args)
guard.hook "#{task_to_supervise.to_s}_begin"
guard.hook "#{task_to_supervise}_begin"
result = guard.send(task_to_supervise, *args)
guard.hook "#{task_to_supervise.to_s}_end"
guard.hook "#{task_to_supervise}_end"
result
rescue Exception
UI.error("#{guard.class.name} guard failed to achieve its <#{task_to_supervise.to_s}> command: #{$!}")
UI.error("#{guard.class.name} guard failed to achieve its <#{task_to_supervise}> command: #{$!}")
::Guard.guards.delete guard
UI.info("Guard #{guard.class.name} has just been fired")
return $!
Expand All @@ -82,9 +82,11 @@ def run
listener.start
end

def add_guard(name, watchers = [], options = {})
def add_guard(name, watchers = [], callbacks = [], options = {})
guard_class = get_guard_class(name)
watchers = watchers.map { |watcher| ::Guard::Watcher.new(watcher[:pattern], watcher[:action]) }
@guards << guard_class.new(watchers, options)
callbacks.each { |callback| ::Guard::Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
end

def get_guard_class(name)
Expand Down
24 changes: 17 additions & 7 deletions lib/guard/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Dsl
class << self
def evaluate_guardfile(options = {})
@@options = options

if File.exists?(guardfile_path)
begin
new.instance_eval(File.read(guardfile_path), guardfile_path, 1)
Expand All @@ -21,7 +21,7 @@ def evaluate_guardfile(options = {})
def guardfile_include?(guard_name)
File.read(guardfile_path).match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/)
end

def guardfile_path
File.join(Dir.pwd, 'Guardfile')
end
Expand All @@ -31,14 +31,24 @@ def group(name, &guard_definition)
guard_definition.call if guard_definition && (@@options[:group].empty? || @@options[:group].include?(name))
end

def guard(name, options = {}, &watch_definition)
@watchers = []
watch_definition.call if watch_definition
::Guard.add_guard(name, @watchers, options)
def guard(name, options = {}, &watch_and_callback_definition)
@watchers = []
@callbacks = []
watch_and_callback_definition.call if watch_and_callback_definition
::Guard.add_guard(name, @watchers, @callbacks, options)
end

def watch(pattern, &action)
@watchers << ::Guard::Watcher.new(pattern, action)
@watchers << { :pattern => pattern, :action => action }
end

def callback(*args, &listener)
listener, events = if args.size > 1
args
else
[listener, args[0]]
end
@callbacks << { :events => events, :listener => listener }
end

end
Expand Down
15 changes: 7 additions & 8 deletions lib/guard/hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ def self.included(base)
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
hook_name = if event.is_a? Symbol
calling_method = caller[0][/`([^']*)'/, 1]
hook_name = "#{calling_method}_#{event}".to_sym
"#{calling_method}_#{event}".to_sym
else
hook_name = event.to_sym
event.to_sym
end

UI.info "\nHook :#{hook_name} executed for #{self.class}"
Expand All @@ -28,15 +27,15 @@ def callbacks
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end

def add(listener, guard_class, events)
_events = events.class == Array ? events : [events]
def add_callback(listener, guard_class, events)
_events = events.is_a?(Array) ? events : [events]
_events.each do |event|
callbacks[[guard_class, event]] << listener
end
end

def has_callback?(listener, guard_class, event)
callbacks[[guard_class, event]].include? listener
callbacks[[guard_class, event]].include?(listener)
end

def notify(guard_class, event)
Expand All @@ -45,7 +44,7 @@ def notify(guard_class, event)
end
end

def reset!
def reset_callbacks!
@callbacks = nil
end
end
Expand Down
63 changes: 41 additions & 22 deletions spec/guard/dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,56 +52,75 @@
end")
end

it "should evaluates only the specified group" do
::Guard.should_receive(:add_guard).with('test', anything, {})
::Guard.should_not_receive(:add_guard).with('another', anything, {})
it "evaluates only the specified group" do
::Guard.should_receive(:add_guard).with('test', anything, anything, {})
::Guard.should_not_receive(:add_guard).with('another', anything, anything, {})
subject.evaluate_guardfile(:group => ['x'])
end

it "should evaluates only the specified groups" do
::Guard.should_receive(:add_guard).with('test', anything, {})
::Guard.should_receive(:add_guard).with('another', anything, {})
it "evaluates only the specified groups" do
::Guard.should_receive(:add_guard).with('test', anything, anything, {})
::Guard.should_receive(:add_guard).with('another', anything, anything, {})
subject.evaluate_guardfile(:group => ['x', 'y'])
end
end

describe "#guard" do
it "should load a guard specified as a string from the DSL" do
it "loads a guard specified by a string" do
mock_guardfile_content("guard 'test'")

::Guard.should_receive(:add_guard).with('test', [], {})
::Guard.should_receive(:add_guard).with('test', [], [], {})
subject.evaluate_guardfile
end

it "should load a guard specified as a symbol from the DSL" do
it "loads a guard specified as a symbol from the DSL" do
mock_guardfile_content("guard :test")

::Guard.should_receive(:add_guard).with(:test, [], {})
::Guard.should_receive(:add_guard).with(:test, [], [], {})
subject.evaluate_guardfile
end

it "should receive options when specified" do
it "accepts options" do
mock_guardfile_content("guard 'test', :opt_a => 1, :opt_b => 'fancy'")

::Guard.should_receive(:add_guard).with('test', anything, { :opt_a => 1, :opt_b => 'fancy' })
::Guard.should_receive(:add_guard).with('test', anything, anything, { :opt_a => 1, :opt_b => 'fancy' })
subject.evaluate_guardfile
end
end

describe "#watch" do
it "should receive watchers when specified" do
it "creates watchers for the guard" do
mock_guardfile_content("
guard 'test' do
watch('a') { 'b' }
watch('c')
end")

::Guard.should_receive(:add_guard).with('test', anything, {}) do |name, watchers, options|
watchers.size.should == 2
watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call
watchers[1].pattern.should == 'c'
watchers[1].action.should be_nil
::Guard.should_receive(:add_guard).with('test', anything, anything, {}) do |name, watchers, callbacks, options|
watchers.should have(2).items
watchers[0][:pattern].should == 'a'
watchers[0][:action].call.should == proc { 'b' }.call
watchers[1][:pattern].should == 'c'
watchers[1][:action].should be_nil
end
subject.evaluate_guardfile
end
end

describe "#callback" do
it "creates callbacks for the guard" do
class MyCustomCallback
end

mock_guardfile_content("
guard 'test' do
callback(:start_end) { 'Guard::Test started!' }
callback(MyCustomCallback, [:start_begin, :run_all_begin])
end")

::Guard.should_receive(:add_guard).with('test', anything, anything, {}) do |name, watchers, callbacks, options|
callbacks.should have(2).items
callbacks[0][:events].should == :start_end
callbacks[0][:listener].call.should == proc { 'Guard::Test started!' }.call
callbacks[1][:events].should == [:start_begin, :run_all_begin]
callbacks[1][:listener].should == MyCustomCallback
end
subject.evaluate_guardfile
end
Expand Down
38 changes: 17 additions & 21 deletions spec/guard/hook_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
require "spec_helper"
require 'guard/guard'
require "guard/hook"
require 'spec_helper'

describe Guard::Hook do
subject { Guard::Hook }

class Guard::Dummy < Guard::Guard
include Guard::Hook

Expand All @@ -16,53 +16,49 @@ def run_all
let(:listener) { double('listener').as_null_object }

context "--module methods--" do
before { subject.add(listener, guard_class, :start_begin) }
before { subject.add_callback(listener, guard_class, :start_begin) }

after { subject.reset! }
after { subject.reset_callbacks! }

describe ".add " do
describe ".add_callback" 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.add_callback(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
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
it "runs only the given callbacks" do
listener2 = double('listener2')
subject.add(listener2, guard_class, :start_end)
subject.add_callback(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
it "runs callbacks only for the guard given" do
guard2_class = double('Guard::Dummy2').class
subject.add(listener, guard2_class, :start_begin)
subject.add_callback(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
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::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

Expand All @@ -74,9 +70,9 @@ def start
end

guard = guard_class.new
Guard::Hook.should_receive(:notify).
with(guard_class, :my_hook)
Guard::Hook.should_receive(:notify).with(guard_class, :my_hook)
guard.start
end
end

end
15 changes: 13 additions & 2 deletions spec/guard_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
::Guard.supervised_task(@g, :regular).should be_true
::Guard.supervised_task(@g, :regular_with_arg, "given_path").should == "i'm a success"
end

it "calls the default hooks" do
@g.should_receive(:hook).with("regular_begin")
@g.should_receive(:hook).with("regular_end")
::Guard.supervised_task(@g, :regular)
end
end

describe "tasks that raise an exception" do
Expand All @@ -91,10 +97,15 @@
failing_result.should be_kind_of(Exception)
failing_result.message.should == 'I break your system'
end
end

it "calls the default hooks"
it "calls the default begin hook but not the default end hook" do
@g.should_receive(:hook).with("failing_begin")
@g.should_not_receive(:hook).with("failing_end")
::Guard.supervised_task(@g, :failing)
end
end
end

end

end

0 comments on commit 44aed32

Please sign in to comment.