Skip to content
Browse files

New Interactions scope feature:

`reload` and `run_all` Guard terminal interactions actions can be scoped to only run on a certain guard or group.
  • Loading branch information...
1 parent 3584dbb commit 64f0b32ceb3d2bfbe652af643cf003b36953aeba Thibaud Guillaume-Gentil committed
Showing with 214 additions and 51 deletions.
  1. +1 −0 CHANGELOG.md
  2. +10 −5 README.md
  3. +41 −25 lib/guard.rb
  4. +76 −10 lib/guard/interactor.rb
  5. +51 −0 spec/guard/interactor_spec.rb
  6. +35 −11 spec/guard_spec.rb
View
1 CHANGELOG.md
@@ -2,6 +2,7 @@
### Improvements
+- `reload` and `run_all` Guard terminal interactions actions can be scoped to only run on a certain guard or group. ([@thibaudgg][])
- Add cli option (-i / --no-interactions) to turn off Guard terminal interactions. ([@thibaudgg][])
- Add support for Growl Notification Transport Protocol. ([@netzpirat][])
- [#157](https://github.com/guard/guard/pull/157): Allow any return from the Guard watchers. ([@earlonrails][])
View
15 README.md
@@ -213,12 +213,17 @@ Interactions
**From version >= 0.7.0 Posix Signal handlers are no more used to interact with Guard. If you're using a version < 0.7, please refer to the [README in the v0.6 branch](https://github.com/guard/guard/blob/v0.6/README.md).**
-When Guard do nothing you can interact with by entering a command + hitting enter:
+When Guard do nothing you can interact with by entering a command + hitting return/enter:
-* `stop|quit|exit|s|q|e + enter` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
-* `reload|r|z + enter` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
-* `pause|p + enter` - Toggle files modification listening. Useful when switching git branches.
-* `just enter (no commands)` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
+* `stop`: `stop|quit|exit|s|q|e + return` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
+* `reload`: `reload|r|z + return` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
+* `pause`: `pause|p + return` - Toggle files modification listening. Useful when switching git branches.
+* `run_all`: `just return (no commands)` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
+
+`reload` and `run_all` actions can be scoped to only run on a certain guard or group. Examples:
+
+* `backend reload + return` - Call only each guard's `#reload` method on backend group.
+* `rspec + return` - Call only rspec guard's `#run_all` method.
Available Guards
----------------
View
66 lib/guard.rb
@@ -138,7 +138,9 @@ def start(options = {})
UI.info "Guard is now watching at '#{ listener.directory }'"
- run_guard_task(:start)
+ run_on_guards do |guard|
+ run_supervised_task(guard, :start)
+ end
interactor.start if interactor
listener.start
@@ -149,7 +151,9 @@ def start(options = {})
def stop
UI.info 'Bye bye...', :reset => true
- run_guard_task(:stop)
+ run_on_guards do |guard|
+ run_supervised_task(guard, :stop)
+ end
listener.stop
abort
@@ -157,17 +161,25 @@ def stop
# Reload all Guards currently enabled.
#
- def reload
+ # @param [Hash] An hash with a guard or a group scope
+ #
+ def reload(scopes)
run do
- run_guard_task(:reload)
+ run_on_guards(scopes) do |guard|
+ run_supervised_task(guard, :reload)
+ end
end
end
# Trigger `run_all` on all Guards currently enabled.
#
- def run_all
+ # @param [Hash] An hash with a guard or a group scope
+ #
+ def run_all(scopes)
run do
- run_guard_task(:run_all)
+ run_on_guards(scopes) do |guard|
+ run_supervised_task(guard, :run_all)
+ end
end
end
@@ -186,9 +198,11 @@ def pause
# Trigger `run_on_change` on all Guards currently enabled.
#
- def run_on_change(paths)
+ def run_on_change(files)
run do
- run_guard_task(:run_on_change, paths)
+ run_on_guards do |guard|
+ run_on_change_task(files, guard)
+ end
end
end
@@ -211,22 +225,22 @@ def run
end
end
- # Loop through all groups and run the given task for each Guard.
+ # Loop through all groups and run the given task (as block) for each Guard.
#
# Stop the task run for the all Guards within a group if one Guard
# throws `:task_has_failed`.
#
- # @param [Symbol] task the task to run
- # @param [Array<String>] files the list of files to pass to the task
+ # @param [Hash] An hash with a guard or a group scope
#
- def run_guard_task(task, files = nil)
- groups.each do |group|
- catch :task_has_failed do
- guards(:group => group.name).each do |guard|
- if task == :run_on_change
- run_on_change_task(files, guard, task)
- else
- run_supervised_task(guard, task)
+ def run_on_guards(scopes = {})
+ if guard = scopes[:guard]
+ yield(guard)
+ else
+ groups = scopes[:group] ? [scopes[:group]] : @groups
+ groups.each do |group|
+ catch :task_has_failed do
+ guards(:group => group.name).each do |guard|
+ yield(guard)
end
end
end
@@ -239,17 +253,16 @@ def run_guard_task(task, files = nil)
#
# @param [Array<String>] files the list of files to pass to the task
# @param [Guard::Guard] guard the guard to run
- # @param [Symbol] task the task to run
# @raise [:task_has_failed] when task has failed
#
- def run_on_change_task(files, guard, task)
+ def run_on_change_task(files, guard)
paths = Watcher.match_files(guard, files)
changes = changed_paths(paths)
deletions = deleted_paths(paths)
unless changes.empty?
- UI.debug "#{ guard.class.name }##{ task } with #{ changes.inspect }"
- run_supervised_task(guard, task, changes)
+ UI.debug "#{ guard.class.name }#run_on_change with #{ changes.inspect }"
+ run_supervised_task(guard, :run_on_change, changes)
end
unless deletions.empty?
@@ -316,7 +329,7 @@ def run_supervised_task(guard, task, *args)
# option set, we do NOT catch it here, it will be catched at the
# group level.
#
- # @see .run_guard_task
+ # @see .run_on_guards
#
# @param [Guard::Guard] guard the Guard to execute
# @return [Symbol] the symbol to catch
@@ -336,6 +349,7 @@ def guard_symbol(guard)
# @param [Array<Watcher>] watchers the list of declared watchers
# @param [Array<Hash>] callbacks the list of callbacks
# @param [Hash] options the Guard options (see the given Guard documentation)
+ # @return [Guard::Guard] the guard added
#
def add_guard(name, watchers = [], callbacks = [], options = {})
if name.to_sym == :ego
@@ -343,7 +357,9 @@ def add_guard(name, watchers = [], callbacks = [], options = {})
else
guard_class = get_guard_class(name)
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
- @guards << guard_class.new(watchers, options)
+ guard = guard_class.new(watchers, options)
+ @guards << guard
+ guard
end
end
View
86 lib/guard/interactor.rb
@@ -10,7 +10,18 @@ module Guard
# - pause, p => Pause Guard
# - Everything else => Run all
#
+ # It's aslo possible to scope to group or guard reload and `run all` actions
+ #
+ # @example `backend reload` will only reload backend group
+ # @example `spork reload` will only reload rspec guard
+ # @example `jasmine` will only run all jasmine specs
+ #
class Interactor
+
+ STOP_ACTIONS = %w[stop quit exit s q e]
+ RELOAD_ACTIONS = %w[reload r z]
+ PAUSE_ACTIONS = %w[pause p]
+
# Start the interactor in its own thread.
#
def start
@@ -19,22 +30,77 @@ def start
if !@thread || @thread.stop?
@thread = Thread.new do
while entry = $stdin.gets.chomp
- case entry
- when 'stop', 'quit', 'exit', 's', 'q', 'e'
- ::Guard.stop
- when 'reload', 'r', 'z'
- ::Guard::Dsl.reevaluate_guardfile
- ::Guard.reload
- when 'pause', 'p'
- ::Guard.pause
- else
- ::Guard.run_all
+ scopes, action = extract_scopes_and_action(entry)
+ case action
+ when :stop
+ ::Guard.stop
+ when :pause
+ ::Guard.pause
+ when :reload
+ ::Guard::Dsl.reevaluate_guardfile if scopes.empty?
+ ::Guard.reload(scopes)
+ when :run_all
+ ::Guard.run_all(scopes)
end
end
end
end
end
+ # Extract guard or group scope and action from Interactor entry
+ #
+ # @example `spork reload` will only reload rspec
+ # @example `jasmine` will only run all jasmine specs
+ #
+ # @param [String] Interactor entry gets from $stdin
+ # @return [Array] entry group or guard scope hash and action
+ def extract_scopes_and_action(entry)
+ scopes = {}
+ entries = entry.split(' ')
+ case entries.length
+ when 1
+ unless action = action_from_entry(entries[0])
+ scopes = scopes_from_entry(entries[0])
+ end
+ when 2
+ scopes = scopes_from_entry(entries[0])
+ action = action_from_entry(entries[1])
+ end
+ action ||= :run_all
+ [scopes, action]
+ end
+
+ # Extract guard or group scope from entry if valid
+ #
+ # @param [String] Interactor entry gets from $stdin
+ # @return [Hash] An hash with a guard or a group scope
+ def scopes_from_entry(entry)
+ scopes = {}
+ if guard = ::Guard.guards(entry)
+ scopes[:guard] = guard
+ end
+ if group = ::Guard.groups(entry)
+ scopes[:group] = group
+ end
+ scopes
+ end
+
+ # Extract action from entry if an existing action is present
+ #
+ # @param [String] Interactor entry gets from $stdin
+ # @return [Symbol] A guard action
+ def action_from_entry(entry)
+ if STOP_ACTIONS.include?(entry)
+ :stop
+ elsif RELOAD_ACTIONS.include?(entry)
+ :reload
+ elsif PAUSE_ACTIONS.include?(entry)
+ :pause
+ end
+ end
+
+ # Kill interactor thread if not current
+ #
def stop_if_not_current
unless Thread.current == @thread
@thread.kill
View
51 spec/guard/interactor_spec.rb
@@ -3,4 +3,55 @@
describe Guard::Interactor do
subject { Guard::Interactor.new }
+ describe "#extract_scopes_and_action" do
+
+ class Guard::Foo < Guard::Guard; end
+ class Guard::FooBar < Guard::Guard; end
+
+ before(:each) do
+ guard = ::Guard.setup
+ @backend_group = guard.add_group(:backend)
+ @frontend_group = guard.add_group(:frontend)
+ @foo_guard = guard.add_guard(:foo, [], [], { :group => :backend })
+ @foo_bar_guard = guard.add_guard('foo-bar', [], [], { :group => :frontend })
+ end
+
+ it "returns :run_all action if entry is blank" do
+ subject.extract_scopes_and_action('').should eql([{}, :run_all])
+ end
+
+ it "returns action if entry is only a action" do
+ subject.extract_scopes_and_action('quit').should eql([{}, :stop])
+ end
+
+ it "returns guard scope and run_all action if entry is only a guard scope" do
+ subject.extract_scopes_and_action('foo-bar').should eql([{ guard: @foo_bar_guard }, :run_all])
+ end
+
+ it "returns group scope and run_all action if entry is only a group scope" do
+ subject.extract_scopes_and_action('backend').should eql([{ group: @backend_group }, :run_all])
+ end
+
+ it "returns run_all action if entry is not a scope" do
+ subject.extract_scopes_and_action('x').should eql([{}, :run_all])
+ end
+
+ it "returns guard scope and action if entry is a guard scope and a action" do
+ subject.extract_scopes_and_action('foo r').should eql([{ guard: @foo_guard }, :reload])
+ end
+
+ it "returns group scope and action if entry is a group scope and a action" do
+ subject.extract_scopes_and_action('frontend z').should eql([{ group: @frontend_group }, :reload])
+ end
+
+ it "returns group scope and run_all action if entry is a group scope and not a action" do
+ subject.extract_scopes_and_action('frontend x').should eql([{ group: @frontend_group }, :run_all])
+ end
+
+ it "returns run_all action if entry is not a scope and not a action" do
+ subject.extract_scopes_and_action('x x').should eql([{}, :run_all])
+ end
+
+ end
+
end
View
46 spec/guard_spec.rb
@@ -392,19 +392,21 @@ class Inline < Guard
end
end
- describe ".run_guard_task" do
+ describe ".run_on_guards" do
subject { ::Guard.setup }
before do
class Guard::Dummy < Guard::Guard; end
+ class Guard::Dumby < Guard::Guard; end
- subject.add_group(:foo, { :halt_on_fail => true })
+ @foo_group = subject.add_group(:foo, { :halt_on_fail => true })
subject.add_group(:bar)
subject.add_guard(:dummy, [], [], { :group => :foo })
subject.add_guard(:dummy, [], [], { :group => :foo })
+ @dumby_guard = subject.add_guard(:dumby, [], [], { :group => :bar })
+ # subject.add_guard(:dummy, [], [], { :group => :bar })
subject.add_guard(:dummy, [], [], { :group => :bar })
- subject.add_guard(:dummy, [], [], { :group => :bar })
- @sum = { :foo => 0, :bar => 0}
+ @sum = { :foo => 0, :bar => 0 }
end
context "all tasks succeed" do
@@ -413,17 +415,37 @@ class Guard::Dummy < Guard::Guard; end
end
it "executes the task for each guard in each group" do
- subject.run_guard_task(:task)
+ subject.run_on_guards do |guard|
+ guard.task
+ end
@sum.all? { |k, v| v == 2 }.should be_true
end
+
+ it "executes the task for each guard in foo group only" do
+ subject.run_on_guards(group: @foo_group) do |guard|
+ guard.task
+ end
+
+ @sum[:foo].should == 2
+ @sum[:bar].should == 0
+ end
+
+ it "executes the task for dumby guard only" do
+ subject.run_on_guards(guard: @dumby_guard) do |guard|
+ guard.task
+ end
+
+ @sum[:foo].should == 0
+ @sum[:bar].should == 1
+ end
end
context "one guard fails" do
before do
- subject.guards.each_with_index do |g, i|
- g.stub!(:task) do
- @sum[g.group] += i+1
+ subject.guards.each_with_index do |guard, i|
+ guard.stub!(:task) do
+ @sum[guard.group] += i+1
if i % 2 == 0
throw :task_has_failed
else
@@ -434,7 +456,9 @@ class Guard::Dummy < Guard::Guard; end
end
it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do
- subject.run_guard_task(:task)
+ subject.run_on_guards do |guard|
+ subject.run_supervised_task(guard, :task)
+ end
@sum[:foo].should eql 1
@sum[:bar].should eql 7
@@ -455,12 +479,12 @@ def watchers
it 'runs the :run_on_change task with the watched file changes' do
Guard.should_receive(:run_supervised_task).with(guard, :run_on_change, ['a.rb', 'b.rb'])
- Guard.run_on_change_task(['a.rb', 'b.rb', 'templates/d.haml'], guard, :run_on_change)
+ Guard.run_on_change_task(['a.rb', 'b.rb', 'templates/d.haml'], guard)
end
it 'runs the :run_on_deletion task with the watched file deletions' do
Guard.should_receive(:run_supervised_task).with(guard, :run_on_deletion, ['c.rb'])
- Guard.run_on_change_task(['!c.rb', '!templates/e.haml'], guard, :run_on_change)
+ Guard.run_on_change_task(['!c.rb', '!templates/e.haml'], guard)
end
end

4 comments on commit 64f0b32

@netzpirat

Very nice feature! Now we only have to wait until Siri is available on OS X so we can speak instead of type.

@rymai
Guard member

:+1:

@netzpirat

OH, we can already speak: https://github.com/floere/james

@thibaudgg
Guard member

nice, but MacRuby only :/

Please sign in to comment.
Something went wrong with that request. Please try again.