guard 2.8.2 → 2.9.0

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