guard 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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