guard 2.7.2 → 2.7.3

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.
@@ -0,0 +1,186 @@
1
+ require "guard/ui"
2
+
3
+ module Guard
4
+ # This class contains useful methods to:
5
+ #
6
+ # * Fetch all the Guard plugins names;
7
+ # * Initialize a plugin, get its location;
8
+ # * Return its class name;
9
+ # * Add its template to the Guardfile.
10
+ #
11
+ class PluginUtil
12
+ ERROR_NO_GUARD_OR_CLASS = "Could not load 'guard/%s' or'\
13
+ ' find class Guard::%s"
14
+
15
+ INFO_ADDED_GUARD_TO_GUARDFILE = "%s guard added to Guardfile,"\
16
+ " feel free to edit it"
17
+
18
+ attr_accessor :name
19
+
20
+ # Returns a list of Guard plugin Gem names installed locally.
21
+ #
22
+ # @return [Array<String>] a list of Guard plugin gem names
23
+ #
24
+ def self.plugin_names
25
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create("1.8.0")
26
+ Gem::Specification.find_all.select do |x|
27
+ if x.name =~ /^guard-/
28
+ true
29
+ elsif x.name != "guard"
30
+
31
+ guard_plugin_path = File.join(
32
+ x.full_gem_path,
33
+ "lib/guard/#{ x.name }.rb"
34
+ )
35
+
36
+ File.exist?(guard_plugin_path)
37
+ end
38
+ end
39
+ else
40
+ ::Guard::UI.deprecation \
41
+ "Rubygems version prior to 1.8.0 are no longer supported"\
42
+ " and may not work"
43
+
44
+ Gem.source_index.find_name(/^guard-/)
45
+ end.map { |x| x.name.sub(/^guard-/, "") }.uniq
46
+ end
47
+
48
+ # Initializes a new `Guard::PluginUtil` object.
49
+ #
50
+ # @param [String] name the name of the Guard plugin
51
+ #
52
+ def initialize(name)
53
+ @name = name.to_s.sub(/^guard-/, "")
54
+ end
55
+
56
+ # Initializes a new `Guard::Plugin` with the given `options` hash. This
57
+ # methods handles plugins that inherit from the deprecated `Guard::Guard`
58
+ # class as well as plugins that inherit from `Guard::Plugin`.
59
+ #
60
+ # @see Guard::Plugin
61
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
62
+ # upgrade for Guard 2.0
63
+ #
64
+ # @return [Guard::Plugin] the initialized plugin
65
+ # @return [Guard::Guard] the initialized plugin. This return type is
66
+ # deprecated and the plugin's maintainer should update it to be
67
+ # compatible with Guard 2.0. For more information on how to upgrade for
68
+ # Guard 2.0, please head over to:
69
+ # https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0
70
+ #
71
+ def initialize_plugin(options)
72
+ klass = plugin_class
73
+ fail "Could not load class: #{_constant_name.inspect}" unless klass
74
+ if klass.superclass.to_s == "Guard::Guard"
75
+ klass.new(options.delete(:watchers), options)
76
+ else
77
+ begin
78
+ klass.new(options)
79
+ rescue ArgumentError => e
80
+ fail "Failed to call #{klass}.new(options): #{e}"
81
+ end
82
+ end
83
+ end
84
+
85
+ # Locates a path to a Guard plugin gem.
86
+ #
87
+ # @return [String] the full path to the plugin gem
88
+ #
89
+ def plugin_location
90
+ @plugin_location ||= begin
91
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create("1.8.0")
92
+ Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
93
+ else
94
+ Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
95
+ end
96
+ end
97
+ rescue
98
+ ::Guard::UI.error "Could not find 'guard-#{ name }' gem path."
99
+ end
100
+
101
+ # Tries to load the Guard plugin main class. This transforms the supplied
102
+ # plugin name into a class name:
103
+ #
104
+ # * `guardname` will become `Guard::Guardname`
105
+ # * `dashed-guard-name` will become `Guard::DashedGuardName`
106
+ # * `underscore_guard_name` will become `Guard::UnderscoreGuardName`
107
+ #
108
+ # When no class is found with the strict case sensitive rules, another
109
+ # try is made to locate the class without matching case:
110
+ #
111
+ # * `rspec` will find a class `Guard::RSpec`
112
+ #
113
+ # @option options [Boolean] fail_gracefully whether error messages should
114
+ # not be printed
115
+ #
116
+ # @return [Class, nil] the loaded class
117
+ #
118
+ def plugin_class(options = {})
119
+ options = { fail_gracefully: false }.merge(options)
120
+
121
+ try_require = false
122
+ begin
123
+ require "guard/#{ name.downcase }" if try_require
124
+
125
+ @plugin_class ||= ::Guard.const_get(_plugin_constant)
126
+ rescue TypeError => error
127
+ if try_require
128
+ ::Guard::UI.error "Could not find class Guard::#{ _constant_name }"
129
+ ::Guard::UI.error error.backtrace.join("\n")
130
+ else
131
+ try_require = true
132
+ retry
133
+ end
134
+ rescue LoadError => error
135
+ unless options[:fail_gracefully]
136
+ UI.error ERROR_NO_GUARD_OR_CLASS % [name.downcase, _constant_name]
137
+ UI.error error.backtrace.join("\n")
138
+ end
139
+ end
140
+ end
141
+
142
+ # Adds a plugin's template to the Guardfile.
143
+ #
144
+ def add_to_guardfile
145
+ msg = "Guard.evaluator not initialized"
146
+ fail msg if ::Guard.evaluator.nil?
147
+ if ::Guard.evaluator.guardfile_include?(name)
148
+ ::Guard::UI.info "Guardfile already includes #{ name } guard"
149
+ else
150
+ content = File.read("Guardfile")
151
+ File.open("Guardfile", "wb") do |f|
152
+ f.puts(content)
153
+ f.puts("")
154
+ f.puts(plugin_class.template(plugin_location))
155
+ end
156
+
157
+ UI.info INFO_ADDED_GUARD_TO_GUARDFILE % name
158
+ end
159
+ end
160
+
161
+ private
162
+
163
+ # Returns the constant for the current plugin.
164
+ #
165
+ # @example Returns the constant for a plugin
166
+ # > Guard::PluginUtil.new('rspec').send(:_plugin_constant)
167
+ # => Guard::RSpec
168
+ #
169
+ def _plugin_constant
170
+ @_plugin_constant ||= ::Guard.constants.detect do |c|
171
+ c.to_s.downcase == _constant_name.downcase
172
+ end
173
+ end
174
+
175
+ # Guesses the most probable name for the current plugin based on its name.
176
+ #
177
+ # @example Returns the most probable name for a plugin
178
+ # > Guard::PluginUtil.new('rspec').send(:_constant_name)
179
+ # => "Rspec"
180
+ #
181
+ def _constant_name
182
+ @_constant_name ||= name.gsub(/\/(.?)/) { "::#{ $1.upcase }" }.
183
+ gsub(/(?:^|[_-])(.)/) { $1.upcase }
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,210 @@
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
+ def run(task, scope_hash = {})
18
+ Lumberjack.unit_of_work do
19
+ _scoped_plugins(scope_hash || {}) do |plugin|
20
+ _supervise(plugin, task) if plugin.respond_to?(task)
21
+ end
22
+ end
23
+ end
24
+
25
+ MODIFICATION_TASKS = [
26
+ :run_on_modifications, :run_on_changes, :run_on_change
27
+ ]
28
+
29
+ ADDITION_TASKS = [:run_on_additions, :run_on_changes, :run_on_change]
30
+ REMOVAL_TASKS = [:run_on_removals, :run_on_changes, :run_on_deletion]
31
+
32
+ # Runs the appropriate tasks on all registered plugins
33
+ # based on the passed changes.
34
+ #
35
+ # @param [Array<String>] modified the modified paths.
36
+ # @param [Array<String>] added the added paths.
37
+ # @param [Array<String>] removed the removed paths.
38
+ #
39
+ def run_on_changes(modified, added, removed)
40
+ types = {
41
+ MODIFICATION_TASKS => modified,
42
+ ADDITION_TASKS => added,
43
+ REMOVAL_TASKS => removed
44
+ }
45
+
46
+ ::Guard::UI.clearable
47
+
48
+ _scoped_plugins do |plugin|
49
+ ::Guard::UI.clear
50
+
51
+ types.each do |tasks, unmatched_paths|
52
+ next if unmatched_paths.empty?
53
+ match_result = ::Guard::Watcher.match_files(plugin, unmatched_paths)
54
+ next if match_result.empty?
55
+
56
+ next unless (task = tasks.detect { |meth| plugin.respond_to?(meth) })
57
+ _supervise(plugin, task, match_result)
58
+ ::Guard::UI.clearable
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 _supervise(plugin, task, *args)
75
+ catch self.class.stopping_symbol_for(plugin) do
76
+ plugin.hook("#{ task }_begin", *args)
77
+ begin
78
+ result = plugin.send(task, *args)
79
+ rescue Interrupt
80
+ throw(:task_has_failed)
81
+ end
82
+ plugin.hook("#{ task }_end", result)
83
+ result
84
+ end
85
+ rescue ScriptError, StandardError, RuntimeError
86
+ ::Guard::UI.error("#{ plugin.class.name } failed to achieve its"\
87
+ " <#{ task }>, exception was:" \
88
+ "\n#{ $!.class }: #{ $!.message }" \
89
+ "\n#{ $!.backtrace.join("\n") }")
90
+ ::Guard.remove_plugin(plugin)
91
+ ::Guard::UI.info("\n#{ plugin.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
+ fail "NO PLUGIN SCOPE" if scopes.nil?
125
+ if plugins = _current_plugins_scope(scopes)
126
+ plugins.each do |guard|
127
+ yield(guard)
128
+ end
129
+ else
130
+ _current_groups_scope(scopes).each do |group|
131
+ fail "Invalid group: #{group.inspect}" unless group.respond_to?(:name)
132
+ current_plugin = nil
133
+ block_return = catch :task_has_failed do
134
+ ::Guard.plugins(group: group.name).each do |guard|
135
+ current_plugin = guard
136
+ yield(guard)
137
+ end
138
+ end
139
+
140
+ next unless block_return.nil?
141
+
142
+ ::Guard::UI.info "#{ current_plugin.class.name } has failed,"\
143
+ " other group's plugins execution has been halted."
144
+ end
145
+ end
146
+ end
147
+
148
+ # Returns the current plugins scope.
149
+ # Local plugins scope wins over global plugins scope.
150
+ # If no plugins scope is found, then NO plugins are returned.
151
+ #
152
+ # @param [Hash] scopes hash with a local plugins or a groups scope
153
+ # @return [Array<Guard::Plugin>] the plugins to scope to
154
+ #
155
+ def _current_plugins_scope(scope)
156
+ return nil unless (plugins = _find_non_empty_plugins_scope(scope))
157
+
158
+ Array(plugins).map { |plugin| _instantiate(:plugin, plugin) }
159
+ end
160
+
161
+ # Returns the current groups scope.
162
+ # Local groups scope wins over global groups scope.
163
+ # If no groups scope is found, then ALL groups are returned.
164
+ #
165
+ # @param [Hash] scopes hash with a local plugins or a groups scope
166
+ # @return [Array<Guard::Group>] the groups to scope to
167
+ #
168
+ def _current_groups_scope(scope)
169
+ <<<<<<< HEAD
170
+ found = _find_non_empty_groups_scope(scope)
171
+ groups = Array(found).map { |group| _instantiate(:group, group) }
172
+ return groups if groups.any? { |g| g.name == :common }
173
+ ([_instantiate(:group, :common)] + Array(found)).compact
174
+ =======
175
+ groups = _find_non_empty_groups_scope(scope)
176
+ Array(groups).map { |group| _instantiate(:group, group) }
177
+ >>>>>>> origin/api_safe_refactoring
178
+ end
179
+
180
+ def _instantiate(meth, obj)
181
+ (obj.is_a?(Symbol) || obj.is_a?(String)) ? ::Guard.send(meth, obj) : obj
182
+ end
183
+
184
+ # Find the first non empty element in the given possibilities
185
+ #
186
+ def _find_non_empty_scope(type, local_scope, *additional_possibilities)
187
+ found = [
188
+ local_scope[:"#{type}s"],
189
+ [local_scope[type.to_sym]],
190
+ ::Guard.scope[:"#{type}s"],
191
+ additional_possibilities.flatten
192
+ ]
193
+ found.compact.detect { |a| !Array(a).compact.empty? }
194
+ end
195
+
196
+ # Find the first non empty plugins scope
197
+ #
198
+ def _find_non_empty_plugins_scope(scope)
199
+ fail "NO PLUGIN SCOPE" if scope.nil?
200
+ _find_non_empty_scope(:plugin, scope)
201
+ end
202
+
203
+ # Find the first non empty groups scope
204
+ #
205
+ def _find_non_empty_groups_scope(scope)
206
+ common = [::Guard.group(:common)]
207
+ common + _find_non_empty_scope(:group, scope, ::Guard.groups)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,395 @@
1
+ require "thread"
2
+ require "listen"
3
+ require "guard/options"
4
+
5
+ module Guard
6
+ # Sets up initial variables and options
7
+ module Setuper
8
+ DEFAULT_OPTIONS = {
9
+ clear: false,
10
+ notify: true,
11
+ debug: false,
12
+ group: [],
13
+ plugin: [],
14
+ watchdir: nil,
15
+ guardfile: nil,
16
+ no_interactions: false,
17
+ no_bundler_warning: false,
18
+ show_deprecations: false,
19
+ latency: nil,
20
+ force_polling: false,
21
+ wait_for_delay: nil,
22
+ listen_on: nil
23
+ }
24
+ DEFAULT_GROUPS = [:common, :default]
25
+
26
+ # Initializes the Guard singleton:
27
+ #
28
+ # * Initialize the internal Guard state;
29
+ # * Create the interactor
30
+ # * Select and initialize the file change listener.
31
+ #
32
+ # @option options [Boolean] clear if auto clear the UI should be done
33
+ # @option options [Boolean] notify if system notifications should be shown
34
+ # @option options [Boolean] debug if debug output should be shown
35
+ # @option options [Array<String>] group the list of groups to start
36
+ # @option options [Array<String>] watchdir the directories to watch
37
+ # @option options [String] guardfile the path to the Guardfile
38
+ # @option options [Boolean] watch_all_modifications **[deprecated]** watches all file modifications if true
39
+ # @option options [Boolean] no_vendor **[deprecated]** ignore vendored dependencies
40
+ #
41
+ # @return [Guard] the Guard singleton
42
+ #
43
+
44
+ # TODO: this method has too many instance variables
45
+ # and some are mock and leak between tests,
46
+ # so ideally there should be a guard "instance"
47
+ # object that can be created anew between tests
48
+ def setup(opts = {})
49
+ reset_options(opts)
50
+ reset_evaluator(opts)
51
+
52
+ @queue = Queue.new
53
+ @runner = ::Guard::Runner.new
54
+ @watchdirs = _setup_watchdirs
55
+
56
+ ::Guard::UI.setup(options)
57
+
58
+ <<<<<<< HEAD
59
+ _setup_debug if options[:debug]
60
+ @listener = _setup_listener
61
+ _setup_signal_traps
62
+ =======
63
+ ::Guard::Deprecator.deprecated_options_warning(options)
64
+ ::Guard::Deprecator.deprecated_plugin_methods_warning
65
+
66
+ _setup_notifier
67
+ _setup_interactor
68
+ >>>>>>> parent of a5162d2... Remove deprecated methods and options. Fixes #425.
69
+
70
+ _load_guardfile
71
+ @interactor = _setup_interactor
72
+ self
73
+ end
74
+
75
+ attr_reader :options, :interactor
76
+
77
+ # Used only by tests (for all I know...)
78
+ def clear_options
79
+ @options = nil
80
+ end
81
+
82
+ # Initializes the groups array with the default group(s).
83
+ #
84
+ # @see DEFAULT_GROUPS
85
+ #
86
+ def reset_groups
87
+ @groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
88
+ end
89
+
90
+ # Initializes the plugins array to an empty array.
91
+ #
92
+ # @see Guard.plugins
93
+ #
94
+ def reset_plugins
95
+ @plugins = []
96
+ end
97
+
98
+ # Initializes the scope hash to `{ groups: [], plugins: [] }`.
99
+ #
100
+ # @see Guard.setup_scope
101
+ #
102
+ def reset_scope
103
+ # calls Guard.scope=() to set the instance variable directly, as opposed
104
+ # to Guard.scope()
105
+ ::Guard.scope = { groups: [], plugins: [] }
106
+ end
107
+
108
+ # Used to merge CLI options with Setuper defaults
109
+ def reset_options(new_options)
110
+ @options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
111
+ end
112
+
113
+ # TODO: code smell - too many reset_* methods
114
+ def reset_evaluator(new_options)
115
+ @evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
116
+ end
117
+
118
+ def save_scope
119
+ # This actually replaces scope from command line,
120
+ # so scope set by 'scope' Pry command will be reset
121
+ @saved_scope = _prepare_scope(::Guard.scope)
122
+ end
123
+
124
+ def restore_scope
125
+ ::Guard.setup_scope(@saved_scope)
126
+ end
127
+
128
+ attr_reader :watchdirs
129
+
130
+ # Stores the scopes defined by the user via the `--group` / `-g` option (to
131
+ # run only a specific group) or the `--plugin` / `-P` option (to run only a
132
+ # specific plugin).
133
+ #
134
+ # @see CLI#start
135
+ # @see Dsl#scope
136
+ #
137
+ def setup_scope(scope = {})
138
+ # TODO: there should be a special Scope class instead
139
+ scope = _prepare_scope(scope)
140
+
141
+ ::Guard.scope = {
142
+ groups: scope[:groups].map { |item| ::Guard.add_group(item) },
143
+ plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
144
+ }
145
+ end
146
+
147
+ # Evaluates the Guardfile content. It displays an error message if no
148
+ # Guard plugins are instantiated after the Guardfile evaluation.
149
+ #
150
+ # @see Guard::Guardfile::Evaluator#evaluate_guardfile
151
+ #
152
+ def evaluate_guardfile
153
+ evaluator.evaluate_guardfile
154
+ msg = "No plugins found in Guardfile, please add at least one."
155
+ ::Guard::UI.error msg unless _pluginless_guardfile?
156
+ end
157
+
158
+ # Asynchronously trigger changes
159
+ #
160
+ # Currently supported args:
161
+ #
162
+ # old style hash: {modified: ['foo'], added: ['bar'], removed: []}
163
+ #
164
+ # new style signals with args: [:guard_pause, :unpaused ]
165
+ #
166
+ def async_queue_add(changes)
167
+ @queue << changes
168
+
169
+ # Putting interactor in background puts guard into foreground
170
+ # so it can handle change notifications
171
+ Thread.new { interactor.background }
172
+ end
173
+
174
+ def pending_changes?
175
+ ! @queue.empty?
176
+ end
177
+
178
+ def add_builtin_plugins(guardfile)
179
+ return unless guardfile
180
+
181
+ pattern = _relative_pathname(guardfile).to_s
182
+ watcher = ::Guard::Watcher.new(pattern)
183
+ ::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
184
+ end
185
+
186
+ private
187
+
188
+ # Sets up various debug behaviors:
189
+ #
190
+ # * Abort threads on exception;
191
+ # * Set the logging level to `:debug`;
192
+ # * Modify the system and ` methods to log themselves before being executed
193
+ #
194
+ # @see #_debug_command_execution
195
+ #
196
+ def _setup_debug
197
+ Thread.abort_on_exception = true
198
+ ::Guard::UI.options[:level] = :debug
199
+ _debug_command_execution
200
+ end
201
+
202
+ # Initializes the listener and registers a callback for changes.
203
+ #
204
+ def _setup_listener
205
+ if options[:listen_on]
206
+ Listen.on(options[:listen_on], &_listener_callback)
207
+ else
208
+ listener_options = {}
209
+ [:latency, :force_polling, :wait_for_delay].each do |option|
210
+ listener_options[option] = options[option] if options[option]
211
+ end
212
+ listen_args = watchdirs + [listener_options]
213
+ Listen.to(*listen_args, &_listener_callback)
214
+ end
215
+ end
216
+
217
+ # Process the change queue, running tasks within the main Guard thread
218
+ def _process_queue
219
+ actions, changes = [], { modified: [], added: [], removed: [] }
220
+
221
+ while pending_changes?
222
+ if (item = @queue.pop).first.is_a?(Symbol)
223
+ actions << item
224
+ else
225
+ item.each { |key, value| changes[key] += value }
226
+ end
227
+ end
228
+
229
+ _run_actions(actions)
230
+ return if changes.values.all?(&:empty?)
231
+ runner.run_on_changes(*changes.values)
232
+ end
233
+
234
+ # Sets up traps to catch signals used to control Guard.
235
+ #
236
+ # Currently two signals are caught:
237
+ # - `USR1` which pauses listening to changes.
238
+ # - `USR2` which resumes listening to changes.
239
+ # - 'INT' which is delegated to Pry if active, otherwise stops Guard.
240
+ #
241
+ def _setup_signal_traps
242
+ return if defined?(JRUBY_VERSION)
243
+
244
+ if Signal.list.keys.include?("USR1")
245
+ Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
246
+ end
247
+
248
+ if Signal.list.keys.include?("USR2")
249
+ Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
250
+ end
251
+
252
+ return unless Signal.list.keys.include?("INT")
253
+ Signal.trap("INT") { interactor.handle_interrupt }
254
+ end
255
+
256
+ # Enables or disables the notifier based on user's configurations.
257
+ #
258
+ def _setup_notifier
259
+ if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
260
+ ::Guard::Notifier.turn_on
261
+ else
262
+ ::Guard::Notifier.turn_off
263
+ end
264
+ end
265
+
266
+ # Adds a command logger in debug mode. This wraps common command
267
+ # execution functions and logs the executed command before execution.
268
+ #
269
+ def _debug_command_execution
270
+ Kernel.send(:alias_method, :original_system, :system)
271
+ Kernel.send(:define_method, :system) do |command, *args|
272
+ ::Guard::UI.debug "Command execution: #{ command } #{ args.join(" ") }"
273
+ Kernel.send :original_system, command, *args
274
+ end
275
+
276
+ Kernel.send(:alias_method, :original_backtick, :'`')
277
+ Kernel.send(:define_method, :'`') do |command|
278
+ ::Guard::UI.debug "Command execution: #{ command }"
279
+ Kernel.send :original_backtick, command
280
+ end
281
+ end
282
+
283
+ # TODO: Guard::Watch or Guard::Scope should provide this
284
+ def _scoped_watchers
285
+ watchers = []
286
+ runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
287
+ watchers
288
+ end
289
+
290
+ # Check if any of the changes are actually watched for
291
+ def _relevant_changes?(changes)
292
+ files = changes.values.flatten(1)
293
+ watchers = _scoped_watchers
294
+ watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
295
+ end
296
+
297
+ def _relative_pathname(path)
298
+ full_path = Pathname(path)
299
+ full_path.relative_path_from(Pathname.pwd)
300
+ rescue ArgumentError
301
+ full_path
302
+ end
303
+
304
+ def _relative_pathnames(paths)
305
+ paths.map { |path| _relative_pathname(path) }
306
+ end
307
+
308
+ def _run_actions(actions)
309
+ actions.each do |action_args|
310
+ args = action_args.dup
311
+ namespaced_action = args.shift
312
+ action = namespaced_action.to_s.sub(/^guard_/, "")
313
+ if ::Guard.respond_to?(action)
314
+ ::Guard.send(action, *args)
315
+ else
316
+ fail "Unknown action: #{action.inspect}"
317
+ end
318
+ end
319
+ end
320
+
321
+ def _setup_watchdirs
322
+ dirs = Array(options[:watchdir])
323
+ dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
324
+ end
325
+
326
+ def _listener_callback
327
+ lambda do |modified, added, removed|
328
+ relative_paths = {
329
+ modified: _relative_pathnames(modified),
330
+ added: _relative_pathnames(added),
331
+ removed: _relative_pathnames(removed)
332
+ }
333
+
334
+ async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
335
+ end
336
+ end
337
+
338
+ def _reset_all
339
+ reset_groups
340
+ reset_plugins
341
+ reset_scope
342
+ end
343
+
344
+ def _setup_interactor
345
+ ::Guard::Interactor.new(options[:no_interactions])
346
+ end
347
+
348
+ def _load_guardfile
349
+ _reset_all
350
+ evaluate_guardfile
351
+ setup_scope
352
+ _setup_notifier
353
+ end
354
+
355
+ def _prepare_scope(scope)
356
+ fail "Guard::setup() not called!" if options.nil?
357
+ plugins = Array(options[:plugin])
358
+ plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
359
+
360
+ # Convert objects to names
361
+ plugins.map! { |p| p.respond_to?(:name) ? p.name : p }
362
+
363
+ groups = Array(options[:group])
364
+ groups = Array(scope[:groups] || scope[:group]) if groups.empty?
365
+
366
+ # Convert objects to names
367
+ groups.map! { |g| g.respond_to?(:name) ? g.name : g }
368
+
369
+ { plugins: plugins, groups: groups }
370
+ end
371
+
372
+ def _pluginless_guardfile?
373
+ # no Reevaluator means there was no Guardfile configured that could be
374
+ # reevaluated, so we don't have a pluginless guardfile, because we don't
375
+ # have a Guardfile to begin with...
376
+ #
377
+ # But, if we have a Guardfile, we'll at least have the built-in
378
+ # Reevaluator, so the following will work:
379
+
380
+ plugins.map(&:name) != ["reevaluator"]
381
+ end
382
+
383
+ def _reset_for_tests
384
+ @options = nil
385
+ @queue = nil
386
+ @runner = nil
387
+ @evaluator = nil
388
+ @watchdirs = nil
389
+ @watchdirs = nil
390
+ @listener = nil
391
+ @interactor = nil
392
+ @scope = nil
393
+ end
394
+ end
395
+ end