Added #callback DSL, modified Guard and Guard::Hook a bit in consequence.

Signed-off-by: Rémy Coutable <remy@jilion.com>
This commit is contained in:
Rémy Coutable 2011-04-16 23:02:13 +02:00 committed by Rémy Coutable
parent b646ae53f6
commit b83653db2e
6 changed files with 100 additions and 64 deletions

View File

@ -58,12 +58,12 @@ module Guard
# Let a guard execute its task but # Let a guard execute its task but
# fire it if his work leads to a system failure # fire it if his work leads to a system failure
def supervised_task(guard, task_to_supervise, *args) 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) result = guard.send(task_to_supervise, *args)
guard.hook "#{task_to_supervise.to_s}_end" guard.hook "#{task_to_supervise}_end"
result result
rescue Exception 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 ::Guard.guards.delete guard
UI.info("Guard #{guard.class.name} has just been fired") UI.info("Guard #{guard.class.name} has just been fired")
return $! return $!
@ -79,9 +79,11 @@ module Guard
listener.start listener.start
end end
def add_guard(name, watchers = [], options = {}) def add_guard(name, watchers = [], callbacks = [], options = {})
guard_class = get_guard_class(name) guard_class = get_guard_class(name)
watchers = watchers.map { |watcher| ::Guard::Watcher.new(watcher[:pattern], watcher[:action]) }
@guards << guard_class.new(watchers, options) @guards << guard_class.new(watchers, options)
callbacks.each { |callback| ::Guard::Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
end end
def get_guard_class(name) def get_guard_class(name)

View File

@ -4,7 +4,7 @@ module Guard
class << self class << self
def evaluate_guardfile(options = {}) def evaluate_guardfile(options = {})
@@options = options @@options = options
if File.exists?(guardfile_path) if File.exists?(guardfile_path)
begin begin
new.instance_eval(File.read(guardfile_path), guardfile_path, 1) new.instance_eval(File.read(guardfile_path), guardfile_path, 1)
@ -21,7 +21,7 @@ module Guard
def guardfile_include?(guard_name) def guardfile_include?(guard_name)
File.read(guardfile_path).match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/) File.read(guardfile_path).match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/)
end end
def guardfile_path def guardfile_path
File.join(Dir.pwd, 'Guardfile') File.join(Dir.pwd, 'Guardfile')
end end
@ -31,14 +31,24 @@ module Guard
guard_definition.call if guard_definition && (@@options[:group].empty? || @@options[:group].include?(name)) guard_definition.call if guard_definition && (@@options[:group].empty? || @@options[:group].include?(name))
end end
def guard(name, options = {}, &watch_definition) def guard(name, options = {}, &watch_and_callback_definition)
@watchers = [] @watchers = []
watch_definition.call if watch_definition @callbacks = []
::Guard.add_guard(name, @watchers, options) watch_and_callback_definition.call if watch_and_callback_definition
::Guard.add_guard(name, @watchers, @callbacks, options)
end end
def watch(pattern, &action) 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
end end

View File

@ -5,17 +5,16 @@ module Guard
end end
module InstanceMethods module InstanceMethods
# When passed a sybmol, #hook will generate a hook name # When passed a sybmol, #hook will generate a hook name
# from the symbol and calling method name. When passed # from the symbol and calling method name. When passed
# a string, #hook will turn the string into a symbol # a string, #hook will turn the string into a symbol
# directly. # directly.
def hook(event) def hook(event)
if event.class == Symbol hook_name = if event.is_a? Symbol
calling_method = caller[0][/`([^']*)'/, 1] calling_method = caller[0][/`([^']*)'/, 1]
hook_name = "#{calling_method}_#{event}".to_sym "#{calling_method}_#{event}".to_sym
else else
hook_name = event.to_sym event.to_sym
end end
UI.info "\nHook :#{hook_name} executed for #{self.class}" UI.info "\nHook :#{hook_name} executed for #{self.class}"
@ -28,15 +27,15 @@ module Guard
@callbacks ||= Hash.new { |hash, key| hash[key] = [] } @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end end
def add(listener, guard_class, events) def add_callback(listener, guard_class, events)
_events = events.class == Array ? events : [events] _events = events.is_a?(Array) ? events : [events]
_events.each do |event| _events.each do |event|
callbacks[[guard_class, event]] << listener callbacks[[guard_class, event]] << listener
end end
end end
def has_callback?(listener, guard_class, event) def has_callback?(listener, guard_class, event)
callbacks[[guard_class, event]].include? listener callbacks[[guard_class, event]].include?(listener)
end end
def notify(guard_class, event) def notify(guard_class, event)
@ -45,7 +44,7 @@ module Guard
end end
end end
def reset! def reset_callbacks!
@callbacks = nil @callbacks = nil
end end
end end

View File

@ -59,56 +59,75 @@ describe Guard::Dsl do
end") end")
end end
it "should evaluates only the specified group" do it "evaluates only the specified group" do
::Guard.should_receive(:add_guard).with('test', anything, {}) ::Guard.should_receive(:add_guard).with('test', anything, anything, {})
::Guard.should_not_receive(:add_guard).with('another', anything, {}) ::Guard.should_not_receive(:add_guard).with('another', anything, anything, {})
subject.evaluate_guardfile(:group => ['x']) subject.evaluate_guardfile(:group => ['x'])
end end
it "should evaluates only the specified groups" do it "evaluates only the specified groups" do
::Guard.should_receive(:add_guard).with('test', anything, {}) ::Guard.should_receive(:add_guard).with('test', anything, anything, {})
::Guard.should_receive(:add_guard).with('another', anything, {}) ::Guard.should_receive(:add_guard).with('another', anything, anything, {})
subject.evaluate_guardfile(:group => ['x', 'y']) subject.evaluate_guardfile(:group => ['x', 'y'])
end end
end end
describe "#guard" do 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'") mock_guardfile_content("guard 'test'")
::Guard.should_receive(:add_guard).with('test', [], [], {})
::Guard.should_receive(:add_guard).with('test', [], {})
subject.evaluate_guardfile subject.evaluate_guardfile
end 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") mock_guardfile_content("guard :test")
::Guard.should_receive(:add_guard).with(:test, [], [], {})
::Guard.should_receive(:add_guard).with(:test, [], {})
subject.evaluate_guardfile subject.evaluate_guardfile
end end
it "should receive options when specified" do it "accepts options" do
mock_guardfile_content("guard 'test', :opt_a => 1, :opt_b => 'fancy'") mock_guardfile_content("guard 'test', :opt_a => 1, :opt_b => 'fancy'")
::Guard.should_receive(:add_guard).with('test', anything, anything, { :opt_a => 1, :opt_b => 'fancy' })
::Guard.should_receive(:add_guard).with('test', anything, { :opt_a => 1, :opt_b => 'fancy' })
subject.evaluate_guardfile subject.evaluate_guardfile
end end
end end
describe "#watch" do describe "#watch" do
it "should receive watchers when specified" do it "creates watchers for the guard" do
mock_guardfile_content(" mock_guardfile_content("
guard 'test' do guard 'test' do
watch('a') { 'b' } watch('a') { 'b' }
watch('c') watch('c')
end") end")
::Guard.should_receive(:add_guard).with('test', anything, {}) do |name, watchers, options| ::Guard.should_receive(:add_guard).with('test', anything, anything, {}) do |name, watchers, callbacks, options|
watchers.size.should == 2 watchers.should have(2).items
watchers[0].pattern.should == 'a' watchers[0][:pattern].should == 'a'
watchers[0].action.call.should == proc { 'b' }.call watchers[0][:action].call.should == proc { 'b' }.call
watchers[1].pattern.should == 'c' watchers[1][:pattern].should == 'c'
watchers[1].action.should be_nil 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 end
subject.evaluate_guardfile subject.evaluate_guardfile
end end

View File

@ -1,8 +1,8 @@
require "spec_helper" require 'spec_helper'
require 'guard/guard'
require "guard/hook"
describe Guard::Hook do describe Guard::Hook do
subject { Guard::Hook }
class Guard::Dummy < Guard::Guard class Guard::Dummy < Guard::Guard
include Guard::Hook include Guard::Hook
@ -16,53 +16,49 @@ describe Guard::Hook do
let(:listener) { double('listener').as_null_object } let(:listener) { double('listener').as_null_object }
context "--module methods--" do 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 it "can add a single callback" do
subject.has_callback?(listener, guard_class, :start_begin).should be_true subject.has_callback?(listener, guard_class, :start_begin).should be_true
end end
it "can add multiple callbacks" do 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, :event1).should be_true
subject.has_callback?(listener, guard_class, :event2).should be_true subject.has_callback?(listener, guard_class, :event2).should be_true
end end
end end
describe ".notify " do describe ".notify" do
it "sends :call to the given Guard class's callbacks" do it "sends :call to the given Guard class's callbacks" do
listener.should_receive(:call).with(guard_class, :start_begin) listener.should_receive(:call).with(guard_class, :start_begin)
subject.notify(guard_class, :start_begin) subject.notify(guard_class, :start_begin)
end end
it "doesn't run callbacks of different types" do it "runs only the given callbacks" do
listener2 = double('listener2') 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) listener2.should_not_receive(:call).with(guard_class, :start_end)
subject.notify(guard_class, :start_begin) subject.notify(guard_class, :start_begin)
end 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 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) listener.should_not_receive(:call).with(guard2_class, :start_begin)
subject.notify(guard_class, :start_begin) subject.notify(guard_class, :start_begin)
end end
end end
end end
describe "#hook " do describe "#hook" do
it "calls Guard::Hook.notify" do it "calls Guard::Hook.notify" do
guard = guard_class.new guard = guard_class.new
Guard::Hook.should_receive(:notify). Guard::Hook.should_receive(:notify).with(guard_class, :run_all_begin)
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_end)
guard.run_all guard.run_all
end end
@ -74,9 +70,9 @@ describe Guard::Hook do
end end
guard = guard_class.new guard = guard_class.new
Guard::Hook.should_receive(:notify). Guard::Hook.should_receive(:notify).with(guard_class, :my_hook)
with(guard_class, :my_hook)
guard.start guard.start
end end
end end
end end

View File

@ -91,6 +91,12 @@ describe Guard do
::Guard.supervised_task(@g, :regular).should be_true ::Guard.supervised_task(@g, :regular).should be_true
::Guard.supervised_task(@g, :regular_with_arg, "given_path").should == "i'm a success" ::Guard.supervised_task(@g, :regular_with_arg, "given_path").should == "i'm a success"
end 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 end
describe "tasks that raise an exception" do describe "tasks that raise an exception" do
@ -106,9 +112,13 @@ describe Guard do
failing_result.should be_kind_of(Exception) failing_result.should be_kind_of(Exception)
failing_result.message.should == 'I break your system' failing_result.message.should == 'I break your system'
end 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
describe ".locate_guard" do describe ".locate_guard" do