Permalink
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...
thibaudgg committed Oct 13, 2011
1 parent 3584dbb commit 64f0b32ceb3d2bfbe652af643cf003b36953aeba
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
@@ -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
@@ -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
@@ -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,25 +151,35 @@ 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
end
# 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,14 +349,17 @@ 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
UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
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
@@ -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
@@ -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
Oops, something went wrong.

4 comments on commit 64f0b32

@netzpirat

This comment has been minimized.

Show comment
Hide comment
@netzpirat

netzpirat Oct 14, 2011

Contributor

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

Contributor

netzpirat replied Oct 14, 2011

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

@rymai

This comment has been minimized.

Show comment
Hide comment
@rymai

rymai Oct 14, 2011

Member

👍

Member

rymai replied Oct 14, 2011

👍

@netzpirat

This comment has been minimized.

Show comment
Hide comment
@netzpirat

netzpirat Oct 14, 2011

Contributor

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

Contributor

netzpirat replied Oct 14, 2011

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

@thibaudgg

This comment has been minimized.

Show comment
Hide comment
@thibaudgg

thibaudgg Oct 14, 2011

Member

nice, but MacRuby only :/

Member

thibaudgg replied Oct 14, 2011

nice, but MacRuby only :/

Please sign in to comment.