From 44aed3264c04478d9c1e03e1bf39d7bd4f93523c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Sat, 16 Apr 2011 23:02:13 +0200 Subject: [PATCH] Added #callback DSL, modified Guard and Guard::Hook a bit in consequence. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/guard.rb | 10 ++++--- lib/guard/dsl.rb | 24 +++++++++++----- lib/guard/hook.rb | 15 +++++----- spec/guard/dsl_spec.rb | 63 +++++++++++++++++++++++++++-------------- spec/guard/hook_spec.rb | 38 +++++++++++-------------- spec/guard_spec.rb | 15 ++++++++-- 6 files changed, 101 insertions(+), 64 deletions(-) diff --git a/lib/guard.rb b/lib/guard.rb index 29e235fdf..20687e3e7 100644 --- a/lib/guard.rb +++ b/lib/guard.rb @@ -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 $! @@ -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) diff --git a/lib/guard/dsl.rb b/lib/guard/dsl.rb index fbbb2ecfa..aee322db6 100644 --- a/lib/guard/dsl.rb +++ b/lib/guard/dsl.rb @@ -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) @@ -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 @@ -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 diff --git a/lib/guard/hook.rb b/lib/guard/hook.rb index 6c62e5fcd..99657bc95 100644 --- a/lib/guard/hook.rb +++ b/lib/guard/hook.rb @@ -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}" @@ -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) @@ -45,7 +44,7 @@ def notify(guard_class, event) end end - def reset! + def reset_callbacks! @callbacks = nil end end diff --git a/spec/guard/dsl_spec.rb b/spec/guard/dsl_spec.rb index 097ff2343..b1f613518 100644 --- a/spec/guard/dsl_spec.rb +++ b/spec/guard/dsl_spec.rb @@ -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 diff --git a/spec/guard/hook_spec.rb b/spec/guard/hook_spec.rb index f9b86ae59..901057ba1 100644 --- a/spec/guard/hook_spec.rb +++ b/spec/guard/hook_spec.rb @@ -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 @@ -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 @@ -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 diff --git a/spec/guard_spec.rb b/spec/guard_spec.rb index e1ec61236..88db1684f 100644 --- a/spec/guard_spec.rb +++ b/spec/guard_spec.rb @@ -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 @@ -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