diff --git a/lib/guard.rb b/lib/guard.rb index 8f225ee..1e14a16 100644 --- a/lib/guard.rb +++ b/lib/guard.rb @@ -16,7 +16,7 @@ module Guard def setup(options = {}) @options = options @guards = [] - @groups = [:default] + @groups = [{ :name => :default, :options => {} }] @interactor = Interactor.new @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd) @@ -39,7 +39,8 @@ module Guard end UI.info "Guard is now watching at '#{listener.directory}'" - guards.each { |guard| supervised_task(guard, :start) } + + execute_supervised_task_for_all_guards(:start) interactor.start listener.start @@ -48,19 +49,19 @@ module Guard def stop UI.info "Bye bye...", :reset => true listener.stop - guards.each { |guard| supervised_task(guard, :stop) } + execute_supervised_task_for_all_guards(:stop) abort end def reload run do - guards.each { |guard| supervised_task(guard, :reload) } + execute_supervised_task_for_all_guards(:reload) end end def run_all run do - guards.each { |guard| supervised_task(guard, :run_all) } + execute_supervised_task_for_all_guards(:run_all) end end @@ -77,13 +78,7 @@ module Guard def run_on_change(files) run do - guards.each do |guard| - paths = Watcher.match_files(guard, files) - unless paths.empty? - UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}" - supervised_task(guard, :run_on_change, paths) - end - end + execute_supervised_task_for_all_guards(:run_on_change, files) end end @@ -99,13 +94,37 @@ module Guard listener.unlock end + def execute_supervised_task_for_all_guards(task, files = nil) + groups.each do |group_hash| + catch :task_has_failed do + guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard| + paths = Watcher.match_files(guard, files) if files + if paths && !paths.empty? + UI.debug "#{guard.class.name}##{task} with #{paths.inspect}" + supervised_task(guard, task, paths) + else + supervised_task(guard, task) + end + end + end + end + end + # 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}_begin", *args) result = guard.send(task_to_supervise, *args) guard.hook("#{task_to_supervise}_end", result) - result + + group = @groups.find { |group| group[:name] == guard.group } + if result === false && group[:options][:halt_on_fail] == true + UI.error "#{guard.class.name}##{task_to_supervise} failed." + throw :task_has_failed + else + result + end + rescue Exception => ex UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" + "\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}") @@ -124,8 +143,8 @@ module Guard end end - def add_group(name) - @groups << name.to_sym unless name.nil? + def add_group(name, options = {}) + @groups << { :name => name.to_sym, :options => options } unless name.nil? || @groups.find { |group| group[:name] == name } end def get_guard_class(name) diff --git a/lib/guard/dsl.rb b/lib/guard/dsl.rb index fdf736b..8c72324 100644 --- a/lib/guard/dsl.rb +++ b/lib/guard/dsl.rb @@ -15,6 +15,7 @@ module Guard def reevaluate_guardfile ::Guard.guards.clear + ::Guard.groups.clear @@options.delete(:guardfile_contents) Dsl.evaluate_guardfile(@@options) msg = "Guardfile has been re-evaluated." @@ -112,11 +113,12 @@ module Guard end - def group(name, &guard_definition) + def group(name, options = {}, &guard_definition) @groups = @@options[:group] || [] name = name.to_sym if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name)) + ::Guard.add_group(name.to_s.downcase.to_sym, options) @current_group = name guard_definition.call @current_group = nil diff --git a/spec/guard/dsl_spec.rb b/spec/guard/dsl_spec.rb index 2050ee6..ed2c049 100644 --- a/spec/guard/dsl_spec.rb +++ b/spec/guard/dsl_spec.rb @@ -9,6 +9,7 @@ describe Guard::Dsl do @local_guardfile_path = File.join(Dir.pwd, 'Guardfile') @home_guardfile_path = File.expand_path(File.join("~", ".Guardfile")) @user_config_path = File.expand_path(File.join("~", ".guard.rb")) + ::Guard.setup ::Guard.stub!(:options).and_return(:debug => true) ::Guard.stub!(:guards).and_return([mock('Guard')]) end @@ -220,16 +221,16 @@ describe Guard::Dsl do describe "#ignore_paths" do disable_user_config - + it "adds the paths to the listener's ignore_paths" do ::Guard.stub!(:listener).and_return(mock('Listener')) ::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz']) - + subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'") ignore_paths.should == ['faz', 'foo', 'bar'] end end - + describe "#group" do disable_user_config @@ -238,6 +239,8 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w']) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] end it "evaluates only the specified symbol group" do @@ -245,15 +248,19 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] end - it "evaluates only the specified groups" do + it "evaluates only the specified groups (with their options)" do ::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with(:rspec, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with(:ronn, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y]) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :x, :options => { :halt_on_fail => true } }, { :name => :y, :options => {} }] end it "evaluates always guard outside any group (even when a group is given)" do @@ -261,9 +268,11 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] end - it "evaluates all groups when no group option is specified" do + it "evaluates all groups when no group option is specified (with their options)" do ::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with(:rspec, [], [], { :group => :x }) @@ -271,6 +280,9 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }, { :name => :x, :options => { :halt_on_fail => true } }, { :name => :y, :options => {} }] + end end @@ -365,7 +377,7 @@ private guard 'test' end - group :x do + group :x, :halt_on_fail => true do guard 'rspec' guard :ronn end diff --git a/spec/guard_spec.rb b/spec/guard_spec.rb index 1a556b4..7a97e46 100644 --- a/spec/guard_spec.rb +++ b/spec/guard_spec.rb @@ -15,7 +15,7 @@ describe Guard do end it "initializes @groups" do - Guard.groups.should eql [:default] + Guard.groups.should eql [{ :name => :default, :options => {} }] end it "initializes the options" do @@ -129,22 +129,25 @@ describe Guard do end end - describe ".add_group" do - before(:each) do - Guard.setup - end + subject { ::Guard.setup } it "accepts group name as string" do - Guard.add_group('backend') + subject.add_group('backend') - Guard.groups.should eql [:default, :backend] + subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => {} }] end it "accepts group name as symbol" do - Guard.add_group(:backend) + subject.add_group(:backend) - Guard.groups.should eql [:default, :backend] + subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => {} }] + end + + it "accepts options" do + subject.add_group(:backend, { :halt_on_fail => true }) + + subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => { :halt_on_fail => true } }] end end @@ -223,12 +226,54 @@ describe Guard do end end + describe ".execute_supervised_task_for_all_guards" do + subject { ::Guard.setup } + + before do + class Guard::Dummy < Guard::Guard; end + + subject.add_group(:foo, { :halt_on_fail => true }) + subject.add_group(:bar) + subject.add_guard(:dummy, [], [], { :group => :foo }) + subject.add_guard(:dummy, [], [], { :group => :foo }) + subject.add_guard(:dummy, [], [], { :group => :bar }) + subject.add_guard(:dummy, [], [], { :group => :bar }) + @sum = { :foo => 0, :bar => 0} + end + + context "all tasks succeed" do + before do + subject.guards.each { |guard| guard.stub!(:task) { @sum[guard.group] += 1; true } } + end + + it "executes the task for each guard in each group" do + subject.execute_supervised_task_for_all_guards(:task) + + @sum.all? { |k, v| v == 2 }.should be_true + end + end + + context "one guard fails (by returning false)" do + before do + subject.guards.each_with_index { |g, i| g.stub!(:task) { @sum[g.group] += i+1; i != 0 && i != 2 } } + end + + it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do + subject.execute_supervised_task_for_all_guards(:task) + + @sum[:foo].should eql 1 + @sum[:bar].should eql 7 + end + end + end + describe ".supervised_task" do subject { ::Guard.setup } - before(:each) do + before do @g = mock(Guard::Guard).as_null_object subject.guards.push(@g) + subject.groups.push({ :name => :foo, :options => { :halt_on_fail => true } }) end context "with a task that succeed" do @@ -278,6 +323,14 @@ describe Guard do end end + context "with a task that return false and guard's group has the :halt_on_fail option == true" do + before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { false } } + + it "throws :task_has_failed" do + expect { subject.supervised_task(@g, :failing) }.to throw_symbol(:task_has_failed) + end + end + context "with a task that raises an exception" do before(:each) { @g.stub!(:failing) { raise "I break your system" } } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1520849..a041ca6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,6 +12,7 @@ RSpec.configure do |config| config.color_enabled = true config.filter_run :focus => true + config.treat_symbols_as_metadata_keys_with_true_values = true config.run_all_when_everything_filtered = true config.before(:each) do