guard 2.7.2 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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