guard 2.8.2 → 2.9.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -7
  3. data/lib/guard.rb +220 -152
  4. data/lib/guard.rb.orig +213 -155
  5. data/lib/guard/aruba_adapter.rb +2 -2
  6. data/lib/guard/cli.rb +8 -13
  7. data/lib/guard/cli.rb.orig +12 -10
  8. data/lib/guard/commander.rb +15 -7
  9. data/lib/guard/commands/all.rb +3 -0
  10. data/lib/guard/commands/change.rb +3 -0
  11. data/lib/guard/commands/pause.rb +2 -0
  12. data/lib/guard/commands/reload.rb +4 -0
  13. data/lib/guard/commands/scope.rb +3 -0
  14. data/lib/guard/config.rb +24 -0
  15. data/lib/guard/deprecated/dsl.rb +45 -0
  16. data/lib/guard/deprecated/guard.rb +166 -0
  17. data/lib/guard/deprecated/guardfile.rb +84 -0
  18. data/lib/guard/dsl.rb +24 -13
  19. data/lib/guard/dsl.rb.orig +378 -0
  20. data/lib/guard/dsl_describer.rb +8 -2
  21. data/lib/guard/dsl_describer.rb.orig +11 -3
  22. data/lib/guard/guardfile.rb +32 -44
  23. data/lib/guard/guardfile/evaluator.rb +13 -6
  24. data/lib/guard/guardfile/generator.rb +4 -3
  25. data/lib/guard/interactor.rb +7 -3
  26. data/lib/guard/internals/debugging.rb +1 -0
  27. data/lib/guard/internals/environment.rb +93 -0
  28. data/lib/guard/internals/helpers.rb +13 -0
  29. data/lib/guard/internals/traps.rb +10 -0
  30. data/lib/guard/jobs/pry_wrapper.rb +4 -3
  31. data/lib/guard/jobs/sleep.rb +2 -0
  32. data/lib/guard/metadata.rb +190 -0
  33. data/lib/guard/notifier.rb +124 -99
  34. data/lib/guard/notifier.rb.orig +124 -99
  35. data/lib/guard/notifier/detected.rb +83 -0
  36. data/lib/guard/notifiers/emacs.rb +2 -1
  37. data/lib/guard/notifiers/tmux.rb +173 -177
  38. data/lib/guard/plugin/base.rb +2 -0
  39. data/lib/guard/plugin_util.rb +26 -32
  40. data/lib/guard/reevaluator.rb +3 -3
  41. data/lib/guard/reevaluator.rb.orig +22 -0
  42. data/lib/guard/runner.rb +1 -0
  43. data/lib/guard/session.rb +5 -0
  44. data/lib/guard/sheller.rb +2 -2
  45. data/lib/guard/templates/Guardfile +4 -0
  46. data/lib/guard/templates/Guardfile.orig +2 -0
  47. data/lib/guard/terminal.rb +1 -0
  48. data/lib/guard/ui.rb +4 -1
  49. data/lib/guard/version.rb +1 -1
  50. data/lib/guard/version.rb.orig +1 -1
  51. data/lib/guard/watcher.rb +3 -1
  52. data/lib/guard/watcher.rb.orig +122 -0
  53. data/man/guard.1 +1 -4
  54. data/man/guard.1.html +1 -4
  55. metadata +17 -25
  56. data/lib/guard/commander.rb.orig +0 -103
  57. data/lib/guard/commands/all.rb.orig +0 -36
  58. data/lib/guard/commands/reload.rb.orig +0 -34
  59. data/lib/guard/commands/scope.rb.orig +0 -36
  60. data/lib/guard/deprecated_methods.rb +0 -72
  61. data/lib/guard/deprecated_methods.rb.orig +0 -71
  62. data/lib/guard/deprecator.rb +0 -133
  63. data/lib/guard/deprecator.rb.orig +0 -206
  64. data/lib/guard/guard.rb +0 -100
  65. data/lib/guard/guard.rb.orig +0 -42
  66. data/lib/guard/guardfile.rb.orig +0 -43
  67. data/lib/guard/guardfile/evaluator.rb.orig +0 -275
  68. data/lib/guard/internals/debugging.rb.orig +0 -0
  69. data/lib/guard/internals/environment.rb.orig +0 -0
  70. data/lib/guard/internals/tracing.rb.orig +0 -0
  71. data/lib/guard/notifiers/base.rb.orig +0 -221
  72. data/lib/guard/notifiers/tmux.rb.orig +0 -339
  73. data/lib/guard/plugin_util.rb.orig +0 -186
  74. data/lib/guard/runner.rb.orig +0 -210
  75. data/lib/guard/setuper.rb +0 -359
  76. data/lib/guard/setuper.rb.orig +0 -395
  77. data/lib/guard/ui.rb.orig +0 -278
@@ -1,186 +0,0 @@
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
@@ -1,210 +0,0 @@
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
data/lib/guard/setuper.rb DELETED
@@ -1,359 +0,0 @@
1
- require "thread"
2
- require "listen"
3
- require "guard/options"
4
-
5
- require "guard/internals/debugging"
6
-
7
- module Guard
8
- # Sets up initial variables and options
9
- module Setuper
10
- DEFAULT_OPTIONS = {
11
- clear: false,
12
- notify: true,
13
- debug: false,
14
- group: [],
15
- plugin: [],
16
- watchdir: nil,
17
- guardfile: nil,
18
- no_interactions: false,
19
- no_bundler_warning: false,
20
- latency: nil,
21
- force_polling: false,
22
- wait_for_delay: nil,
23
- listen_on: nil
24
- }
25
- DEFAULT_GROUPS = [:common, :default]
26
-
27
- # Initializes the Guard singleton:
28
- #
29
- # * Initialize the internal Guard state;
30
- # * Create the interactor
31
- # * Select and initialize the file change listener.
32
- #
33
- # @option options [Boolean] clear if auto clear the UI should be done
34
- # @option options [Boolean] notify if system notifications should be shown
35
- # @option options [Boolean] debug if debug output should be shown
36
- # @option options [Array<String>] group the list of groups to start
37
- # @option options [Array<String>] watchdir the directories to watch
38
- # @option options [String] guardfile the path to the Guardfile
39
- #
40
- # @return [Guard] the Guard singleton
41
- #
42
-
43
- # TODO: this method has too many instance variables
44
- # and some are mock and leak between tests,
45
- # so ideally there should be a guard "instance"
46
- # object that can be created anew between tests
47
- def setup(opts = {})
48
- # NOTE: must be set before anything calls Guard.options
49
- reset_options(opts)
50
-
51
- # NOTE: must be set before anything calls Guard::UI.debug
52
- ::Guard::Internals::Debugging.start if options[:debug]
53
-
54
- reset_evaluator(opts)
55
-
56
- @queue = Queue.new
57
- @runner = ::Guard::Runner.new
58
- @watchdirs = _setup_watchdirs
59
-
60
- ::Guard::UI.reset_and_clear
61
-
62
- @listener = _setup_listener
63
- _setup_signal_traps
64
-
65
- _load_guardfile
66
- @interactor = _setup_interactor
67
- self
68
- end
69
-
70
- attr_reader :options, :interactor
71
-
72
- # Used only by tests (for all I know...)
73
- def clear_options
74
- @options = nil
75
- end
76
-
77
- # Initializes the groups array with the default group(s).
78
- #
79
- # @see DEFAULT_GROUPS
80
- #
81
- def reset_groups
82
- @groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
83
- end
84
-
85
- # Initializes the plugins array to an empty array.
86
- #
87
- # @see Guard.plugins
88
- #
89
- def reset_plugins
90
- @plugins = []
91
- end
92
-
93
- # Initializes the scope hash to `{ groups: [], plugins: [] }`.
94
- #
95
- # @see Guard.setup_scope
96
- #
97
- def reset_scope
98
- # calls Guard.scope=() to set the instance variable directly, as opposed
99
- # to Guard.scope()
100
- ::Guard.scope = { groups: [], plugins: [] }
101
- end
102
-
103
- # Used to merge CLI options with Setuper defaults
104
- def reset_options(new_options)
105
- @options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
106
- end
107
-
108
- # TODO: code smell - too many reset_* methods
109
- def reset_evaluator(new_options)
110
- @evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
111
- end
112
-
113
- def save_scope
114
- # This actually replaces scope from command line,
115
- # so scope set by 'scope' Pry command will be reset
116
- @saved_scope = _prepare_scope(::Guard.scope)
117
- end
118
-
119
- def restore_scope
120
- ::Guard.setup_scope(@saved_scope)
121
- end
122
-
123
- attr_reader :watchdirs
124
-
125
- # Stores the scopes defined by the user via the `--group` / `-g` option (to
126
- # run only a specific group) or the `--plugin` / `-P` option (to run only a
127
- # specific plugin).
128
- #
129
- # @see CLI#start
130
- # @see Dsl#scope
131
- #
132
- def setup_scope(scope = {})
133
- # TODO: there should be a special Scope class instead
134
- scope = _prepare_scope(scope)
135
-
136
- ::Guard.scope = {
137
- groups: scope[:groups].map { |item| ::Guard.add_group(item) },
138
- plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
139
- }
140
- end
141
-
142
- # Evaluates the Guardfile content. It displays an error message if no
143
- # Guard plugins are instantiated after the Guardfile evaluation.
144
- #
145
- # @see Guard::Guardfile::Evaluator#evaluate_guardfile
146
- #
147
- def evaluate_guardfile
148
- evaluator.evaluate_guardfile
149
- msg = "No plugins found in Guardfile, please add at least one."
150
- ::Guard::UI.error msg unless _pluginless_guardfile?
151
- end
152
-
153
- # Asynchronously trigger changes
154
- #
155
- # Currently supported args:
156
- #
157
- # old style hash: {modified: ['foo'], added: ['bar'], removed: []}
158
- #
159
- # new style signals with args: [:guard_pause, :unpaused ]
160
- #
161
- def async_queue_add(changes)
162
- @queue << changes
163
-
164
- # Putting interactor in background puts guard into foreground
165
- # so it can handle change notifications
166
- Thread.new { interactor.background }
167
- end
168
-
169
- def pending_changes?
170
- ! @queue.empty?
171
- end
172
-
173
- def add_builtin_plugins(guardfile)
174
- return unless guardfile
175
-
176
- pattern = _relative_pathname(guardfile).to_s
177
- watcher = ::Guard::Watcher.new(pattern)
178
- ::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
179
- end
180
-
181
- private
182
-
183
- # Initializes the listener and registers a callback for changes.
184
- #
185
- def _setup_listener
186
- if options[:listen_on]
187
- Listen.on(options[:listen_on], &_listener_callback)
188
- else
189
- listener_options = {}
190
- [:latency, :force_polling, :wait_for_delay].each do |option|
191
- listener_options[option] = options[option] if options[option]
192
- end
193
- listen_args = watchdirs + [listener_options]
194
- Listen.to(*listen_args, &_listener_callback)
195
- end
196
- end
197
-
198
- # Process the change queue, running tasks within the main Guard thread
199
- def _process_queue
200
- actions, changes = [], { modified: [], added: [], removed: [] }
201
-
202
- while pending_changes?
203
- if (item = @queue.pop).first.is_a?(Symbol)
204
- actions << item
205
- else
206
- item.each { |key, value| changes[key] += value }
207
- end
208
- end
209
-
210
- _run_actions(actions)
211
- return if changes.values.all?(&:empty?)
212
- runner.run_on_changes(*changes.values)
213
- end
214
-
215
- # Sets up traps to catch signals used to control Guard.
216
- #
217
- # Currently two signals are caught:
218
- # - `USR1` which pauses listening to changes.
219
- # - `USR2` which resumes listening to changes.
220
- # - 'INT' which is delegated to Pry if active, otherwise stops Guard.
221
- #
222
- def _setup_signal_traps
223
- return if defined?(JRUBY_VERSION)
224
-
225
- if Signal.list.keys.include?("USR1")
226
- Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
227
- end
228
-
229
- if Signal.list.keys.include?("USR2")
230
- Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
231
- end
232
-
233
- return unless Signal.list.keys.include?("INT")
234
- Signal.trap("INT") { interactor.handle_interrupt }
235
- end
236
-
237
- # Enables or disables the notifier based on user's configurations.
238
- #
239
- def _setup_notifier
240
- if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
241
- ::Guard::Notifier.turn_on
242
- else
243
- ::Guard::Notifier.turn_off
244
- end
245
- end
246
-
247
- # TODO: Guard::Watch or Guard::Scope should provide this
248
- def _scoped_watchers
249
- watchers = []
250
- runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
251
- watchers
252
- end
253
-
254
- # Check if any of the changes are actually watched for
255
- def _relevant_changes?(changes)
256
- files = changes.values.flatten(1)
257
- watchers = _scoped_watchers
258
- watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
259
- end
260
-
261
- def _relative_pathname(path)
262
- full_path = Pathname(path)
263
- full_path.relative_path_from(Pathname.pwd)
264
- rescue ArgumentError
265
- full_path
266
- end
267
-
268
- def _relative_pathnames(paths)
269
- paths.map { |path| _relative_pathname(path) }
270
- end
271
-
272
- def _run_actions(actions)
273
- actions.each do |action_args|
274
- args = action_args.dup
275
- namespaced_action = args.shift
276
- action = namespaced_action.to_s.sub(/^guard_/, "")
277
- if ::Guard.respond_to?(action)
278
- ::Guard.send(action, *args)
279
- else
280
- fail "Unknown action: #{action.inspect}"
281
- end
282
- end
283
- end
284
-
285
- def _setup_watchdirs
286
- dirs = Array(options[:watchdir])
287
- dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
288
- end
289
-
290
- def _listener_callback
291
- lambda do |modified, added, removed|
292
- relative_paths = {
293
- modified: _relative_pathnames(modified),
294
- added: _relative_pathnames(added),
295
- removed: _relative_pathnames(removed)
296
- }
297
-
298
- async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
299
- end
300
- end
301
-
302
- def _reset_all
303
- reset_groups
304
- reset_plugins
305
- reset_scope
306
- end
307
-
308
- def _setup_interactor
309
- ::Guard::Interactor.new(options[:no_interactions])
310
- end
311
-
312
- def _load_guardfile
313
- _reset_all
314
- evaluate_guardfile
315
- setup_scope
316
- _setup_notifier
317
- end
318
-
319
- def _prepare_scope(scope)
320
- fail "Guard::setup() not called!" if options.nil?
321
- plugins = Array(options[:plugin])
322
- plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
323
-
324
- # Convert objects to names
325
- plugins.map! { |p| p.respond_to?(:name) ? p.name : p }
326
-
327
- groups = Array(options[:group])
328
- groups = Array(scope[:groups] || scope[:group]) if groups.empty?
329
-
330
- # Convert objects to names
331
- groups.map! { |g| g.respond_to?(:name) ? g.name : g }
332
-
333
- { plugins: plugins, groups: groups }
334
- end
335
-
336
- def _pluginless_guardfile?
337
- # no Reevaluator means there was no Guardfile configured that could be
338
- # reevaluated, so we don't have a pluginless guardfile, because we don't
339
- # have a Guardfile to begin with...
340
- #
341
- # But, if we have a Guardfile, we'll at least have the built-in
342
- # Reevaluator, so the following will work:
343
-
344
- plugins.map(&:name) != ["reevaluator"]
345
- end
346
-
347
- def _reset_for_tests
348
- @options = nil
349
- @queue = nil
350
- @runner = nil
351
- @evaluator = nil
352
- @watchdirs = nil
353
- @watchdirs = nil
354
- @listener = nil
355
- @interactor = nil
356
- @scope = nil
357
- end
358
- end
359
- end