guard 2.6.1 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -58
  3. data/bin/guard +2 -2
  4. data/lib/guard.rb +64 -59
  5. data/lib/guard/cli.rb +66 -60
  6. data/lib/guard/cli.rb.orig +215 -0
  7. data/lib/guard/commander.rb +45 -69
  8. data/lib/guard/commands/all.rb +21 -19
  9. data/lib/guard/commands/change.rb +17 -22
  10. data/lib/guard/commands/notification.rb +15 -16
  11. data/lib/guard/commands/pause.rb +14 -15
  12. data/lib/guard/commands/reload.rb +19 -20
  13. data/lib/guard/commands/scope.rb +23 -19
  14. data/lib/guard/commands/show.rb +13 -16
  15. data/lib/guard/deprecated_methods.rb +6 -10
  16. data/lib/guard/deprecator.rb +52 -37
  17. data/lib/guard/dsl.rb +55 -33
  18. data/lib/guard/dsl_describer.rb +83 -31
  19. data/lib/guard/dsl_describer.rb.orig +184 -0
  20. data/lib/guard/group.rb +7 -6
  21. data/lib/guard/guard.rb +4 -4
  22. data/lib/guard/guard.rb.orig +42 -0
  23. data/lib/guard/guardfile.rb +12 -13
  24. data/lib/guard/guardfile/evaluator.rb +77 -55
  25. data/lib/guard/guardfile/evaluator.rb.orig +275 -0
  26. data/lib/guard/guardfile/generator.rb +25 -20
  27. data/lib/guard/interactor.rb +52 -293
  28. data/lib/guard/interactor.rb.orig +85 -0
  29. data/lib/guard/jobs/base.rb +21 -0
  30. data/lib/guard/jobs/pry_wrapper.rb +290 -0
  31. data/lib/guard/jobs/pry_wrapper.rb.orig +293 -0
  32. data/lib/guard/jobs/sleep.rb +25 -0
  33. data/lib/guard/notifier.rb +42 -39
  34. data/lib/guard/notifiers/base.rb +25 -24
  35. data/lib/guard/notifiers/emacs.rb +30 -24
  36. data/lib/guard/notifiers/file_notifier.rb +3 -7
  37. data/lib/guard/notifiers/gntp.rb +22 -22
  38. data/lib/guard/notifiers/growl.rb +16 -15
  39. data/lib/guard/notifiers/libnotify.rb +7 -10
  40. data/lib/guard/notifiers/notifysend.rb +15 -14
  41. data/lib/guard/notifiers/rb_notifu.rb +8 -10
  42. data/lib/guard/notifiers/terminal_notifier.rb +15 -11
  43. data/lib/guard/notifiers/terminal_title.rb +4 -8
  44. data/lib/guard/notifiers/tmux.rb +104 -71
  45. data/lib/guard/options.rb +1 -5
  46. data/lib/guard/plugin.rb +1 -3
  47. data/lib/guard/plugin/base.rb +12 -9
  48. data/lib/guard/plugin/hooker.rb +1 -5
  49. data/lib/guard/plugin_util.rb +46 -25
  50. data/lib/guard/plugin_util.rb.orig +178 -0
  51. data/lib/guard/rake_task.rb +4 -7
  52. data/lib/guard/reevaluator.rb +13 -0
  53. data/lib/guard/runner.rb +50 -78
  54. data/lib/guard/runner.rb.orig +200 -0
  55. data/lib/guard/setuper.rb +199 -130
  56. data/lib/guard/setuper.rb.orig +348 -0
  57. data/lib/guard/sheller.rb +107 -0
  58. data/lib/guard/tags +367 -0
  59. data/lib/guard/ui.rb +50 -38
  60. data/lib/guard/ui.rb.orig +254 -0
  61. data/lib/guard/ui/colors.rb +17 -21
  62. data/lib/guard/version.rb +1 -1
  63. data/lib/guard/version.rb.orig +3 -0
  64. data/lib/guard/watcher.rb +49 -62
  65. metadata +21 -4
  66. data/lib/guard/notifiers/growl_notify.rb +0 -93
@@ -0,0 +1,200 @@
1
+ require "lumberjack"
2
+
3
+ require "guard/ui"
4
+ require "guard/watcher"
5
+
6
+ module Guard
7
+ # The runner is responsible for running all methods defined on each plugin.
8
+ #
9
+ class Runner
10
+ # Runs a Guard-task on all registered plugins.
11
+ #
12
+ # @param [Symbol] task the task to run
13
+ #
14
+ # @param [Hash] scopes either the Guard plugin or the group to run the task
15
+ # on
16
+ #
17
+ # @see self.run_supervised_task
18
+ #
19
+ def run(task, scope = {})
20
+ Lumberjack.unit_of_work do
21
+ _scoped_plugins(scope) do |guard|
22
+ run_supervised_task(guard, task) if guard.respond_to?(task)
23
+ end
24
+ end
25
+ end
26
+
27
+ MODIFICATION_TASKS = [
28
+ :run_on_modifications, :run_on_changes, :run_on_change
29
+ ]
30
+
31
+ ADDITION_TASKS = [:run_on_additions, :run_on_changes, :run_on_change]
32
+ REMOVAL_TASKS = [:run_on_removals, :run_on_changes, :run_on_deletion]
33
+
34
+ # Runs the appropriate tasks on all registered plugins
35
+ # based on the passed changes.
36
+ #
37
+ # @param [Array<String>] modified the modified paths.
38
+ # @param [Array<String>] added the added paths.
39
+ # @param [Array<String>] removed the removed paths.
40
+ #
41
+ def run_on_changes(modified, added, removed)
42
+ types = {
43
+ MODIFICATION_TASKS => modified,
44
+ ADDITION_TASKS => added,
45
+ REMOVAL_TASKS => removed
46
+ }
47
+
48
+ ::Guard::UI.clearable
49
+
50
+ _scoped_plugins do |guard|
51
+ ::Guard::UI.clear
52
+
53
+ types.each do |tasks, unmatched_paths|
54
+ paths = ::Guard::Watcher.match_files(guard, unmatched_paths)
55
+ next if paths.empty?
56
+
57
+ next unless (task = tasks.detect { |meth| guard.respond_to?(meth) })
58
+ run_supervised_task(guard, task, paths)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Run a Guard plugin task, but remove the Guard plugin when his work leads
64
+ # to a system failure.
65
+ #
66
+ # When the Group has `:halt_on_fail` disabled, we've to catch
67
+ # `:task_has_failed` here in order to avoid an uncaught throw error.
68
+ #
69
+ # @param [Guard::Plugin] guard the Guard to execute
70
+ # @param [Symbol] task the task to run
71
+ # @param [Array] args the arguments for the task
72
+ # @raise [:task_has_failed] when task has failed
73
+ #
74
+ def run_supervised_task(guard, task, *args)
75
+ catch self.class.stopping_symbol_for(guard) do
76
+ guard.hook("#{ task }_begin", *args)
77
+ begin
78
+ result = guard.send(task, *args)
79
+ rescue Interrupt
80
+ throw(:task_has_failed)
81
+ end
82
+ guard.hook("#{ task }_end", result)
83
+ result
84
+ end
85
+ rescue ScriptError, StandardError, RuntimeError
86
+ ::Guard::UI.error("#{ guard.class.name } failed to achieve its"\
87
+ " <#{ task }>, exception was:" \
88
+ "\n#{ $!.class }: #{ $!.message }" \
89
+ "\n#{ $!.backtrace.join("\n") }")
90
+ ::Guard.plugins.delete guard
91
+ ::Guard::UI.info("\n#{ guard.class.name } has just been fired")
92
+ $!
93
+ end
94
+
95
+ # Returns the symbol that has to be caught when running a supervised task.
96
+ #
97
+ # @note If a Guard group is being run and it has the `:halt_on_fail`
98
+ # option set, this method returns :no_catch as it will be caught at the
99
+ # group level.
100
+ # @see ._scoped_plugins
101
+ #
102
+ # @param [Guard::Plugin] guard the Guard plugin to execute
103
+ # @return [Symbol] the symbol to catch
104
+ #
105
+ def self.stopping_symbol_for(guard)
106
+ guard.group.options[:halt_on_fail] ? :no_catch : :task_has_failed
107
+ end
108
+
109
+ private
110
+
111
+ # Loop through all groups and run the given task for each Guard plugin.
112
+ #
113
+ # If no scope is supplied, the global Guard scope is taken into account.
114
+ # If both a plugin and a group scope is given, then only the plugin scope
115
+ # is used.
116
+ #
117
+ # Stop the task run for the all Guard plugins within a group if one Guard
118
+ # throws `:task_has_failed`.
119
+ #
120
+ # @param [Hash] scopes hash with plugins or a groups scope
121
+ # @yield the task to run
122
+ #
123
+ def _scoped_plugins(scopes = {})
124
+ if plugins = _current_plugins_scope(scopes)
125
+ plugins.each do |guard|
126
+ yield(guard)
127
+ end
128
+ else
129
+ _current_groups_scope(scopes).each do |group|
130
+ current_plugin = nil
131
+ block_return = catch :task_has_failed do
132
+ ::Guard.plugins(group: group.name).each do |guard|
133
+ current_plugin = guard
134
+ yield(guard)
135
+ end
136
+ end
137
+
138
+ next unless block_return.nil?
139
+
140
+ ::Guard::UI.info "#{ current_plugin.class.name } has failed,"\
141
+ " other group's plugins execution has been halted."
142
+ end
143
+ end
144
+ end
145
+
146
+ # Returns the current plugins scope.
147
+ # Local plugins scope wins over global plugins scope.
148
+ # If no plugins scope is found, then NO plugins are returned.
149
+ #
150
+ # @param [Hash] scopes hash with a local plugins or a groups scope
151
+ # @return [Array<Guard::Plugin>] the plugins to scope to
152
+ #
153
+ def _current_plugins_scope(scope)
154
+ if plugins = _find_non_empty_plugins_scope(scope)
155
+ Array(plugins).map do |plugin|
156
+ plugin.is_a?(Symbol) ? ::Guard.plugin(plugin) : plugin
157
+ end
158
+ else
159
+ nil
160
+ end
161
+ end
162
+
163
+ # Returns the current groups scope.
164
+ # Local groups scope wins over global groups scope.
165
+ # If no groups scope is found, then ALL groups are returned.
166
+ #
167
+ # @param [Hash] scopes hash with a local plugins or a groups scope
168
+ # @return [Array<Guard::Group>] the groups to scope to
169
+ #
170
+ def _current_groups_scope(scope)
171
+ Array(_find_non_empty_groups_scope(scope)).map do |group|
172
+ group.is_a?(Symbol) ? ::Guard.group(group) : group
173
+ end
174
+ end
175
+
176
+ # Find the first non empty element in the given possibilities
177
+ #
178
+ def _find_non_empty_scope(type, local_scope, *additional_possibilities)
179
+ found = [
180
+ local_scope[:"#{type}s"],
181
+ local_scope[type.to_sym],
182
+ ::Guard.scope[:"#{type}s"],
183
+ additional_possibilities.flatten
184
+ ].compact.detect { |a| !Array(a).empty? }
185
+ found ? [::Guard.group(:common)] + Array(found) : found
186
+ end
187
+
188
+ # Find the first non empty plugins scope
189
+ #
190
+ def _find_non_empty_plugins_scope(scope)
191
+ _find_non_empty_scope(:plugin, scope)
192
+ end
193
+
194
+ # Find the first non empty groups scope
195
+ #
196
+ def _find_non_empty_groups_scope(scope)
197
+ _find_non_empty_scope(:group, scope, ::Guard.groups)
198
+ end
199
+ end
200
+ end
data/lib/guard/setuper.rb CHANGED
@@ -1,11 +1,10 @@
1
- require 'thread'
2
- require 'listen'
3
- require 'guard/options'
1
+ require "thread"
2
+ require "listen"
3
+ require "guard/options"
4
4
 
5
5
  module Guard
6
-
6
+ # Sets up initial variables and options
7
7
  module Setuper
8
-
9
8
  DEFAULT_OPTIONS = {
10
9
  clear: false,
11
10
  notify: true,
@@ -22,12 +21,12 @@ module Guard
22
21
  wait_for_delay: nil,
23
22
  listen_on: nil
24
23
  }
25
- DEFAULT_GROUPS = [:default]
24
+ DEFAULT_GROUPS = [:default, :common]
26
25
 
27
26
  # Initializes the Guard singleton:
28
27
  #
29
28
  # * Initialize the internal Guard state;
30
- # * Create the interactor when necessary for user interaction;
29
+ # * Create the interactor
31
30
  # * Select and initialize the file change listener.
32
31
  #
33
32
  # @option options [Boolean] clear if auto clear the UI should be done
@@ -39,59 +38,33 @@ module Guard
39
38
  #
40
39
  # @return [Guard] the Guard singleton
41
40
  #
41
+
42
+ # TODO: this method has too many instance variables
43
+ # and some are mock and leak between tests,
44
+ # so ideally there should be a guard "instance"
45
+ # object that can be created anew between tests
42
46
  def setup(opts = {})
43
- _reset_lazy_accessors
44
- @running = true
45
- @lock = Mutex.new
46
- @opts = opts
47
- @watchdirs = [Dir.pwd]
48
- @runner = ::Guard::Runner.new
49
-
50
- if options[:watchdir]
51
- # Ensure we have an array
52
- @watchdirs = Array(options[:watchdir]).map { |dir| File.expand_path dir }
53
- end
47
+ reset_options(opts)
48
+ reset_evaluator(opts)
49
+
50
+ @queue = Queue.new
51
+ @runner = ::Guard::Runner.new
52
+ @watchdirs = _setup_watchdirs
54
53
 
55
54
  ::Guard::UI.clear(force: true)
55
+
56
56
  _setup_debug if options[:debug]
57
- _setup_listener
57
+ @listener = _setup_listener
58
58
  _setup_signal_traps
59
59
 
60
- reset_groups
61
- reset_plugins
62
- reset_scope
63
-
64
- evaluate_guardfile
65
-
66
- setup_scope(groups: options[:group], plugins: options[:plugin])
67
-
68
- _setup_notifier
69
-
60
+ _load_guardfile
61
+ @interactor = _setup_interactor
70
62
  self
71
63
  end
72
64
 
73
- # Lazy initializer for Guard's options hash
74
- #
75
- def options
76
- @options ||= ::Guard::Options.new(@opts, DEFAULT_OPTIONS)
77
- end
78
-
79
- # Lazy initializer for Guardfile evaluator
80
- #
81
- def evaluator
82
- @evaluator ||= ::Guard::Guardfile::Evaluator.new(@opts || {})
83
- end
84
-
85
- # Lazy initializer the interactor unless the user has specified not to.
86
- #
87
- def interactor
88
- return if options[:no_interactions] || !::Guard::Interactor.enabled
89
-
90
- @interactor ||= ::Guard::Interactor.new
91
- end
65
+ attr_reader :options, :evaluator, :interactor
92
66
 
93
- # Clear Guard's options hash
94
- #
67
+ # Used only by tests (for all I know...)
95
68
  def clear_options
96
69
  @options = nil
97
70
  end
@@ -117,25 +90,48 @@ module Guard
117
90
  # @see Guard.setup_scope
118
91
  #
119
92
  def reset_scope
120
- @scope = { groups: [], plugins: [] }
93
+ # calls Guard.scope=() to set the instance variable directly, as opposed
94
+ # to Guard.scope()
95
+ ::Guard.scope = { groups: [], plugins: [] }
96
+ end
97
+
98
+ # Used to merge CLI options with Setuper defaults
99
+ def reset_options(new_options)
100
+ @options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
101
+ end
102
+
103
+ # TODO: code smell - too many reset_* methods
104
+ def reset_evaluator(new_options)
105
+ @evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
106
+ end
107
+
108
+ def save_scope
109
+ # This actually replaces scope from command line,
110
+ # so scope set by 'scope' Pry command will be reset
111
+ @saved_scope = _prepare_scope(::Guard.scope)
112
+ end
113
+
114
+ def restore_scope
115
+ ::Guard.setup_scope(@saved_scope)
121
116
  end
122
117
 
123
118
  attr_reader :watchdirs
124
119
 
125
- # Stores the scopes defined by the user via the `--group` / `-g` option (to run
126
- # only a specific group) or the `--plugin` / `-P` option (to run only a
120
+ # Stores the scopes defined by the user via the `--group` / `-g` option (to
121
+ # run only a specific group) or the `--plugin` / `-P` option (to run only a
127
122
  # specific plugin).
128
123
  #
129
124
  # @see CLI#start
130
125
  # @see Dsl#scope
131
126
  #
132
- def setup_scope(new_scope)
133
- if new_scope[:groups] && new_scope[:groups].any?
134
- scope[:groups] = new_scope[:groups].map { |group| ::Guard.add_group(group) }
135
- end
136
-
137
- if new_scope[:plugins] && new_scope[:plugins].any?
138
- scope[:plugins] = new_scope[:plugins].map { |plugin| ::Guard.plugin(plugin) }
127
+ def setup_scope(scope = {})
128
+ # TODO: there should be a special Scope class instead
129
+ scope = _prepare_scope(scope)
130
+ { groups: :add_group, plugins: :plugin }.each do |type, meth|
131
+ next unless scope[type].any?
132
+ ::Guard.scope[type] = scope[type].map do |item|
133
+ ::Guard.send(meth, item)
134
+ end
139
135
  end
140
136
  end
141
137
 
@@ -146,17 +142,40 @@ module Guard
146
142
  #
147
143
  def evaluate_guardfile
148
144
  evaluator.evaluate_guardfile
149
- ::Guard::UI.error 'No plugins found in Guardfile, please add at least one.' if plugins.empty?
145
+ msg = "No plugins found in Guardfile, please add at least one."
146
+ ::Guard::UI.error msg unless _non_builtin_plugins?
150
147
  end
151
148
 
152
- private
149
+ # Asynchronously trigger changes
150
+ #
151
+ # Currently supported args:
152
+ #
153
+ # old style hash: {modified: ['foo'], added: ['bar'], removed: []}
154
+ #
155
+ # new style signals with args: [:guard_pause, :unpaused ]
156
+ #
157
+ def async_queue_add(changes)
158
+ @queue << changes
153
159
 
154
- def _reset_lazy_accessors
155
- @options = nil
156
- @evaluator = nil
157
- @interactor = nil
160
+ # Putting interactor in background puts guard into foreground
161
+ # so it can handle change notifications
162
+ Thread.new { interactor.background }
158
163
  end
159
164
 
165
+ def pending_changes?
166
+ ! @queue.empty?
167
+ end
168
+
169
+ def add_builtin_plugins(guardfile)
170
+ return unless guardfile
171
+
172
+ pattern = _relative_pathname(guardfile).to_s
173
+ watcher = ::Guard::Watcher.new(pattern)
174
+ ::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
175
+ end
176
+
177
+ private
178
+
160
179
  # Sets up various debug behaviors:
161
180
  #
162
181
  # * Abort threads on exception;
@@ -175,15 +194,31 @@ module Guard
175
194
  #
176
195
  def _setup_listener
177
196
  if options[:listen_on]
178
- @listener = Listen.on(options[:listen_on], &_listener_callback)
197
+ Listen.on(options[:listen_on], &_listener_callback)
179
198
  else
180
199
  listener_options = {}
181
200
  [:latency, :force_polling, :wait_for_delay].each do |option|
182
201
  listener_options[option] = options[option] if options[option]
183
202
  end
184
203
  listen_args = watchdirs + [listener_options]
185
- @listener = Listen.to(*listen_args, &_listener_callback)
204
+ Listen.to(*listen_args, &_listener_callback)
205
+ end
206
+ end
207
+
208
+ # Process the change queue, running tasks within the main Guard thread
209
+ def _process_queue
210
+ actions, changes = [], { modified: [], added: [], removed: [] }
211
+
212
+ while pending_changes?
213
+ if (item = @queue.pop).first.is_a?(Symbol)
214
+ actions << item
215
+ else
216
+ item.each { |key, value| changes[key] += value }
217
+ end
186
218
  end
219
+
220
+ _run_actions(actions)
221
+ runner.run_on_changes(*changes.values)
187
222
  end
188
223
 
189
224
  # Sets up traps to catch signals used to control Guard.
@@ -194,39 +229,24 @@ module Guard
194
229
  # - 'INT' which is delegated to Pry if active, otherwise stops Guard.
195
230
  #
196
231
  def _setup_signal_traps
197
- unless defined?(JRUBY_VERSION)
198
- if Signal.list.keys.include?('USR1')
199
- Signal.trap('USR1') do
200
- unless listener.paused?
201
- Thread.new { within_preserved_state { ::Guard.pause } }
202
- end
203
- end
204
- end
232
+ return if defined?(JRUBY_VERSION)
205
233
 
206
- if Signal.list.keys.include?('USR2')
207
- Signal.trap('USR2') do
208
- if listener.paused?
209
- Thread.new { within_preserved_state { ::Guard.pause } }
210
- end
211
- end
212
- end
234
+ if Signal.list.keys.include?("USR1")
235
+ Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
236
+ end
213
237
 
214
- if Signal.list.keys.include?('INT')
215
- Signal.trap('INT') do
216
- if interactor && interactor.thread
217
- interactor.thread.raise(Interrupt)
218
- else
219
- ::Guard.stop
220
- end
221
- end
222
- end
238
+ if Signal.list.keys.include?("USR2")
239
+ Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
223
240
  end
241
+
242
+ return unless Signal.list.keys.include?("INT")
243
+ Signal.trap("INT") { interactor.handle_interrupt }
224
244
  end
225
245
 
226
246
  # Enables or disables the notifier based on user's configurations.
227
247
  #
228
248
  def _setup_notifier
229
- if options[:notify] && ENV['GUARD_NOTIFY'] != 'false'
249
+ if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
230
250
  ::Guard::Notifier.turn_on
231
251
  else
232
252
  ::Guard::Notifier.turn_off
@@ -239,7 +259,7 @@ module Guard
239
259
  def _debug_command_execution
240
260
  Kernel.send(:alias_method, :original_system, :system)
241
261
  Kernel.send(:define_method, :system) do |command, *args|
242
- ::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
262
+ ::Guard::UI.debug "Command execution: #{ command } #{ args.join(" ") }"
243
263
  Kernel.send :original_system, command, *args
244
264
  end
245
265
 
@@ -250,53 +270,102 @@ module Guard
250
270
  end
251
271
  end
252
272
 
273
+ # TODO: Guard::Watch or Guard::Scope should provide this
274
+ def _scoped_watchers
275
+ watchers = []
276
+ runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
277
+ watchers
278
+ end
279
+
253
280
  # Check if any of the changes are actually watched for
254
- #
255
- # NOTE: this is called from the listen thread - be careful to not
256
- # modify any state
257
- #
258
- # TODO: move this to watcher class?
259
- #
260
281
  def _relevant_changes?(changes)
261
- # TODO: make a Guardfile reloader "plugin" instead of a special case
262
- return true if ::Guard::Watcher.match_guardfile?(changes[:modified])
282
+ files = changes.values.flatten(1)
283
+ watchers = _scoped_watchers
284
+ watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
285
+ end
263
286
 
264
- # TODO: ignoring irrelevant files should be Listen's responsibility
265
- all_files = changes.values.flatten(1)
266
- runner.send(:_scoped_plugins) do |guard|
267
- return true if ::Guard::Watcher.match_files?([guard], all_files)
268
- end
269
- false
270
- end
271
-
272
- def _relative_paths(changes)
273
- # Convert to relative paths (respective to the watchdir it came from)
274
- watchdirs.each do |watchdir|
275
- changes.each do |type, paths|
276
- paths.each do |path|
277
- if path.start_with? watchdir
278
- path.sub! "#{watchdir}#{File::SEPARATOR}", ''
279
- end
280
- end
287
+ def _relative_pathname(path)
288
+ full_path = Pathname(path)
289
+ full_path.relative_path_from(Pathname.pwd)
290
+ rescue ArgumentError
291
+ full_path
292
+ end
293
+
294
+ def _relative_pathnames(paths)
295
+ paths.map { |path| _relative_pathname(path) }
296
+ end
297
+
298
+ def _run_actions(actions)
299
+ actions.each do |action_args|
300
+ args = action_args.dup
301
+ namespaced_action = args.shift
302
+ action = namespaced_action.to_s.sub(/^guard_/, "")
303
+ if ::Guard.respond_to?(action)
304
+ ::Guard.send(action, *args)
305
+ else
306
+ fail "Unknown action: #{action.inspect}"
281
307
  end
282
308
  end
283
309
  end
284
310
 
311
+ def _setup_watchdirs
312
+ dirs = Array(options[:watchdir])
313
+ dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
314
+ end
315
+
285
316
  def _listener_callback
286
317
  lambda do |modified, added, removed|
287
- all_changes = { modified: modified.dup,
288
- added: added.dup,
289
- removed: removed.dup }
290
-
291
- # TODO: this should be Listen's responsibility
292
- _relative_paths(all_changes)
318
+ relative_paths = {
319
+ modified: _relative_pathnames(modified),
320
+ added: _relative_pathnames(added),
321
+ removed: _relative_pathnames(removed)
322
+ }
293
323
 
294
- if _relevant_changes?(all_changes)
295
- within_preserved_state do
296
- runner.run_on_changes(*all_changes.values)
297
- end
298
- end
324
+ async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
299
325
  end
300
326
  end
327
+
328
+ def _reset_all
329
+ reset_groups
330
+ reset_plugins
331
+ reset_scope
332
+ end
333
+
334
+ def _setup_interactor
335
+ ::Guard::Interactor.new(options[:no_interactions])
336
+ end
337
+
338
+ def _load_guardfile
339
+ _reset_all
340
+ evaluate_guardfile
341
+ setup_scope
342
+ _setup_notifier
343
+ end
344
+
345
+ def _prepare_scope(scope)
346
+ plugins = Array(options[:plugin])
347
+ plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
348
+
349
+ groups = Array(options[:group])
350
+ groups = Array(scope[:groups] || scope[:group]) if groups.empty?
351
+
352
+ { plugins: plugins, groups: groups }
353
+ end
354
+
355
+ def _non_builtin_plugins?
356
+ plugins.map(&:name) != ["reevaluator"]
357
+ end
358
+
359
+ def _reset_for_tests
360
+ @options = nil
361
+ @queue = nil
362
+ @runner = nil
363
+ @evaluator = nil
364
+ @watchdirs = nil
365
+ @watchdirs = nil
366
+ @listener = nil
367
+ @interactor = nil
368
+ ::Guard.scope = nil
369
+ end
301
370
  end
302
371
  end