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
data/lib/guard.rb.orig CHANGED
@@ -1,213 +1,271 @@
1
- require "rbconfig"
1
+ require "thread"
2
+ require "listen"
3
+
4
+ require "guard/config"
5
+ require "guard/deprecated/guard" unless Guard::Config.new.strict?
6
+
7
+ require "guard/internals/debugging"
8
+ require "guard/internals/traps"
9
+ require "guard/internals/helpers"
10
+
11
+ require "guard/metadata"
12
+ require "guard/options"
2
13
 
3
14
  require "guard/commander"
4
- require "guard/deprecated_methods"
5
- require "guard/deprecator"
6
15
  require "guard/dsl"
7
- require "guard/group"
8
- require "guard/guardfile"
9
16
  require "guard/interactor"
10
17
  require "guard/notifier"
11
18
  require "guard/plugin_util"
12
19
  require "guard/runner"
13
- require "guard/setuper"
14
20
  require "guard/sheller"
15
21
  require "guard/ui"
16
22
  require "guard/watcher"
17
- require "guard/reevaluator"
18
23
 
19
24
  # Guard is the main module for all Guard related modules and classes.
20
25
  # Also Guard plugins should use this namespace.
21
26
  #
22
27
  module Guard
23
- DEV_NULL = Gem.win_platform? ? "NUL" : "/dev/null"
24
-
25
- extend Commander
26
- extend DeprecatedMethods
27
- extend Setuper
28
+ Deprecated::Guard.add_deprecated(self) unless Config.new.strict?
28
29
 
29
30
  class << self
30
- # Called by Pry scope command
31
+ attr_reader :listener
31
32
 
32
- def scope=(new_scope)
33
- @scope = new_scope
34
- @scope.dup.freeze
35
- end
33
+ include Internals::Helpers
36
34
 
37
- def scope
38
- <<<<<<< HEAD
39
- fail "::Guard.setup() not called" if @scope.nil?
40
- =======
41
- >>>>>>> origin/api_safe_refactoring
42
- @scope.dup.freeze
43
- end
44
- attr_reader :runner, :listener
45
-
46
- # Smart accessor for retrieving specific plugins at once.
47
- #
48
- # @see Guard.plugin
49
- # @see Guard.group
50
- # @see Guard.groups
51
- #
52
- # @example Filter plugins by String or Symbol
53
- # Guard.plugins('rspec')
54
- # Guard.plugins(:rspec)
35
+ # Initializes the Guard singleton:
55
36
  #
56
- # @example Filter plugins by Regexp
57
- # Guard.plugins(/rsp.+/)
37
+ # * Initialize the internal Guard state;
38
+ # * Create the interactor
39
+ # * Select and initialize the file change listener.
58
40
  #
59
- # @example Filter plugins by Hash
60
- # Guard.plugins(name: 'rspec', group: 'backend')
41
+ # @option options [Boolean] clear if auto clear the UI should be done
42
+ # @option options [Boolean] notify if system notifications should be shown
43
+ # @option options [Boolean] debug if debug output should be shown
44
+ # @option options [Array<String>] group the list of groups to start
45
+ # @option options [Array<String>] watchdir the directories to watch
46
+ # @option options [String] guardfile the path to the Guardfile
61
47
  #
62
- # @param [String, Symbol, Regexp, Hash] filter the filter to apply to the
63
- # plugins
64
- # @return [Plugin, Array<Plugin>] the filtered plugin(s)
48
+ # @return [Guard] the Guard singleton
65
49
  #
66
- def plugins(filter = nil)
67
- @plugins ||= []
68
50
 
69
- return @plugins if filter.nil?
51
+ # TODO: this method has too many instance variables
52
+ # and some are mock and leak between tests,
53
+ # so ideally there should be a guard "instance"
54
+ # object that can be created anew between tests
55
+ def setup(opts = {})
56
+ # NOTE: must be set before anything calls Guard.options
57
+ reset_options(opts)
70
58
 
71
- filtered_plugins = case filter
72
- when String, Symbol
73
- @plugins.select do |plugin|
74
- plugin.name == filter.to_s.downcase.gsub("-", "")
75
- end
76
- when Regexp
77
- @plugins.select do |plugin|
78
- plugin.name =~ filter
79
- end
80
- when Hash
81
- @plugins.select do |plugin|
82
- filter.all? do |k, v|
83
- case k
84
- when :name
85
- plugin.name == v.to_s.downcase.gsub("-", "")
86
- when :group
87
- plugin.group.name == v.to_sym
88
- end
89
- end
90
- end
91
- end
59
+ # NOTE: must be set before anything calls Guard::UI.debug
60
+ ::Guard::Internals::Debugging.start if options[:debug]
92
61
 
93
- filtered_plugins
62
+ @queue = Queue.new
63
+ self.watchdirs = Array(options[:watchdir])
64
+
65
+ ::Guard::UI.reset_and_clear
66
+
67
+ @listener = _setup_listener
68
+
69
+ _reset_all
70
+ evaluate_guardfile
71
+ setup_scope
72
+
73
+ ::Guard::Notifier.connect(notify: options[:notify])
74
+
75
+ traps = Internals::Traps
76
+ traps.handle("USR1") { async_queue_add([:guard_pause, :paused]) }
77
+ traps.handle("USR2") { async_queue_add([:guard_pause, :unpaused]) }
78
+
79
+ @interactor = ::Guard::Interactor.new(options[:no_interactions])
80
+ traps.handle("INT") { @interactor.handle_interrupt }
81
+
82
+ self
94
83
  end
95
84
 
96
- # Smart accessor for retrieving a specific plugin.
85
+ attr_reader :interactor
86
+
87
+ # Used only by tests (for all I know...)
88
+ def clear_options
89
+ @options = nil
90
+ end
91
+
92
+ # Initializes the groups array with the default group(s).
97
93
  #
98
- # @see Guard.plugins
99
- # @see Guard.group
100
- # @see Guard.groups
94
+ # @see DEFAULT_GROUPS
101
95
  #
102
- # @example Find a plugin by String or Symbol
103
- # Guard.plugin('rspec')
104
- # Guard.plugin(:rspec)
96
+ def reset_groups
97
+ @groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
98
+ end
99
+
100
+ # Initializes the plugins array to an empty array.
105
101
  #
106
- # @example Find a plugin by Regexp
107
- # Guard.plugin(/rsp.+/)
102
+ # @see Guard.plugins
108
103
  #
109
- # @example Find a plugin by Hash
110
- # Guard.plugin(name: 'rspec', group: 'backend')
104
+ def reset_plugins
105
+ @plugins = []
106
+ end
107
+
108
+ attr_reader :watchdirs
109
+
110
+ # Stores the scopes defined by the user via the `--group` / `-g` option (to
111
+ # run only a specific group) or the `--plugin` / `-P` option (to run only a
112
+ # specific plugin).
111
113
  #
112
- # @param [String, Symbol, Regexp, Hash] filter the filter for finding the
113
- # plugin the Guard plugin
114
- # @return [Plugin, nil] the plugin found, nil otherwise
114
+ # @see CLI#start
115
+ # @see Dsl#scope
115
116
  #
116
- def plugin(filter)
117
- plugins(filter).first
117
+ def setup_scope(scope = {})
118
+ # TODO: there should be a special Scope class instead
119
+ scope = _prepare_scope(scope)
120
+
121
+ ::Guard.scope = {
122
+ groups: scope[:groups].map { |item| ::Guard.add_group(item) },
123
+ plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
124
+ }
118
125
  end
119
126
 
120
- # Smart accessor for retrieving specific groups at once.
127
+ # Evaluates the Guardfile content. It displays an error message if no
128
+ # Guard plugins are instantiated after the Guardfile evaluation.
121
129
  #
122
- # @see Guard.plugin
123
- # @see Guard.plugins
124
- # @see Guard.group
130
+ # @see Guard::Guardfile::Evaluator#evaluate_guardfile
131
+ #
132
+ def evaluate_guardfile
133
+ evaluator = Guard::Guardfile::Evaluator.new(options)
134
+ evaluator.evaluate_guardfile
135
+ msg = "No plugins found in Guardfile, please add at least one."
136
+ ::Guard::UI.error msg if _pluginless_guardfile?
137
+ end
138
+
139
+ # Asynchronously trigger changes
125
140
  #
126
- # @example Filter groups by String or Symbol
127
- # Guard.groups('backend')
128
- # Guard.groups(:backend)
141
+ # Currently supported args:
129
142
  #
130
- # @example Filter groups by Regexp
131
- # Guard.groups(/(back|front)end/)
143
+ # old style hash: {modified: ['foo'], added: ['bar'], removed: []}
132
144
  #
133
- # @param [String, Symbol, Regexp] filter the filter to apply to the Groups
134
- # @return [Array<Group>] the filtered group(s)
145
+ # new style signals with args: [:guard_pause, :unpaused ]
135
146
  #
136
- def groups(filter = nil)
137
- @groups ||= []
147
+ def async_queue_add(changes)
148
+ @queue << changes
138
149
 
139
- return @groups if filter.nil?
150
+ # Putting interactor in background puts guard into foreground
151
+ # so it can handle change notifications
152
+ Thread.new { interactor.background }
153
+ end
140
154
 
141
- case filter
142
- when String, Symbol
143
- @groups.select { |group| group.name == filter.to_sym }
144
- when Regexp
145
- @groups.select { |group| group.name.to_s =~ filter }
155
+ def pending_changes?
156
+ ! @queue.empty?
157
+ end
158
+
159
+ def watchdirs=(dirs)
160
+ dirs = [Dir.pwd] if dirs.empty?
161
+ @watchdirs = dirs.map { |dir| File.expand_path dir }
162
+ end
163
+
164
+ private
165
+
166
+ # Initializes the listener and registers a callback for changes.
167
+ #
168
+ def _setup_listener
169
+ if options[:listen_on]
170
+ Listen.on(options[:listen_on], &_listener_callback)
146
171
  else
147
- fail "Invalid filter: #{filter.inspect}"
172
+ listener_options = {}
173
+ [:latency, :force_polling, :wait_for_delay].each do |option|
174
+ listener_options[option] = options[option] if options[option]
175
+ end
176
+ listen_args = watchdirs + [listener_options]
177
+ Listen.to(*listen_args, &_listener_callback)
148
178
  end
149
179
  end
150
180
 
151
- # Smart accessor for retrieving a specific group.
152
- #
153
- # @see Guard.plugin
154
- # @see Guard.plugins
155
- # @see Guard.groups
156
- #
157
- # @example Find a group by String or Symbol
158
- # Guard.group('backend')
159
- # Guard.group(:backend)
160
- #
161
- # @example Find a group by Regexp
162
- # Guard.group(/(back|front)end/)
163
- #
164
- # @param [String, Symbol, Regexp] filter the filter for finding the group
165
- # @return [Group] the group found, nil otherwise
166
- #
167
- def group(filter)
168
- groups(filter).first
181
+ # Process the change queue, running tasks within the main Guard thread
182
+ def _process_queue
183
+ actions, changes = [], { modified: [], added: [], removed: [] }
184
+
185
+ while pending_changes?
186
+ if (item = @queue.pop).first.is_a?(Symbol)
187
+ actions << item
188
+ else
189
+ item.each { |key, value| changes[key] += value }
190
+ end
191
+ end
192
+
193
+ _run_actions(actions)
194
+ return if changes.values.all?(&:empty?)
195
+ Runner.new.run_on_changes(*changes.values)
169
196
  end
170
197
 
171
- # Add a Guard plugin to use.
172
- #
173
- # @param [String] name the Guard name
174
- # @param [Hash] options the plugin options (see Plugin documentation)
175
- # @option options [String] group the group to which the plugin belongs
176
- # @option options [Array<Watcher>] watchers the list of declared watchers
177
- # @option options [Array<Hash>] callbacks the list of callbacks
178
- # @return [Plugin] the added Guard plugin
179
- # @see Plugin
180
- #
181
- def add_plugin(name, options = {})
182
- instance = ::Guard::PluginUtil.new(name).initialize_plugin(options)
183
- @plugins << instance
184
- instance
198
+ # TODO: Guard::Watch or Guard::Scope should provide this
199
+ def _scoped_watchers
200
+ watchers = []
201
+ Runner.new.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
202
+ watchers
185
203
  end
186
204
 
187
- # Used by runner to remove a failed plugin
188
- def remove_plugin(plugin)
189
- # TODO: coverage/aruba
190
- @plugins.delete(plugin)
205
+ # Check if any of the changes are actually watched for
206
+ def _relevant_changes?(changes)
207
+ files = changes.values.flatten(1)
208
+ watchers = _scoped_watchers
209
+ watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
191
210
  end
192
211
 
193
- # Add a Guard plugin group.
194
- #
195
- # @param [String] name the group name
196
- # @option options [Boolean] halt_on_fail if a task execution
197
- # should be halted for all Guard plugins in this group if
198
- # one Guard throws `:task_has_failed`
199
- # @return [Group] the group added (or retrieved from the `@groups`
200
- # variable if already present)
201
- #
202
- # @see Group
203
- #
204
- def add_group(name, options = {})
205
- unless (group = group(name))
206
- group = ::Guard::Group.new(name, options)
207
- @groups << group
212
+ def _relative_pathnames(paths)
213
+ paths.map { |path| _relative_pathname(path) }
214
+ end
215
+
216
+ def _run_actions(actions)
217
+ actions.each do |action_args|
218
+ args = action_args.dup
219
+ namespaced_action = args.shift
220
+ action = namespaced_action.to_s.sub(/^guard_/, "")
221
+ if ::Guard.respond_to?(action)
222
+ ::Guard.send(action, *args)
223
+ else
224
+ fail "Unknown action: #{action.inspect}"
225
+ end
226
+ end
227
+ end
228
+
229
+ def _listener_callback
230
+ lambda do |modified, added, removed|
231
+ relative_paths = {
232
+ modified: _relative_pathnames(modified),
233
+ added: _relative_pathnames(added),
234
+ removed: _relative_pathnames(removed)
235
+ }
236
+
237
+ async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
208
238
  end
239
+ end
240
+
241
+ def _reset_all
242
+ reset_groups
243
+ reset_plugins
244
+ reset_scope
245
+ end
246
+
247
+ def _pluginless_guardfile?
248
+ # no Reevaluator means there was no Guardfile configured that could be
249
+ # reevaluated, so we don't have a pluginless guardfile, because we don't
250
+ # have a Guardfile to begin with...
251
+ #
252
+ # But, if we have a Guardfile, we'll at least have the built-in
253
+ # Reevaluator, so the following will work:
254
+
255
+ # TODO: this is a workaround for tests
256
+ return true if plugins.empty?
257
+
258
+ plugins.map(&:name) == ["reevaluator"]
259
+ end
209
260
 
210
- group
261
+ def _reset_for_tests
262
+ @options = nil
263
+ @queue = nil
264
+ @watchdirs = nil
265
+ @watchdirs = nil
266
+ @listener = nil
267
+ @interactor = nil
268
+ @scope = nil
211
269
  end
212
270
  end
213
271
  end
@@ -38,8 +38,8 @@ module Guard
38
38
  # The ruby interpreter would pipe this to STDERR and exit 1 in the case
39
39
  # of an unhandled exception
40
40
  b = e.backtrace
41
- b.unshift("#{b.shift}: #{e.message} (#{e.class})")
42
- @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
41
+ @stderr.puts "#{b.shift}: #{e.message} (#{e.class})"
42
+ @stderr.puts b.map { |s| "\tfrom #{s}" }.join("\n")
43
43
  1
44
44
  rescue SystemExit => e
45
45
  e.status
data/lib/guard/cli.rb CHANGED
@@ -3,7 +3,8 @@ require "thor"
3
3
  require "guard"
4
4
  require "guard/version"
5
5
  require "guard/dsl_describer"
6
- require "guard/guardfile"
6
+ require "guard/guardfile/evaluator"
7
+ require "guard/guardfile/generator"
7
8
 
8
9
  module Guard
9
10
  # Facade for the Guard command line interface managed by
@@ -70,12 +71,6 @@ module Guard
70
71
  aliases: "-B",
71
72
  banner: "Turn off warning when Bundler is not present"
72
73
 
73
- # DEPRECATED
74
- method_option :show_deprecations,
75
- type: :boolean,
76
- default: false,
77
- banner: "DEPRECATED: it does nothing"
78
-
79
74
  # Listen options
80
75
  method_option :latency,
81
76
  type: :numeric,
@@ -131,6 +126,7 @@ module Guard
131
126
  # @see Guard::DslDescriber.notifiers
132
127
  #
133
128
  def notifiers
129
+ ::Guard.reset_options(options)
134
130
  ::Guard::DslDescriber.new(options).notifiers
135
131
  end
136
132
 
@@ -170,24 +166,23 @@ module Guard
170
166
  _verify_bundler_presence unless options[:no_bundler_warning]
171
167
 
172
168
  ::Guard.reset_options(options) # Since UI.deprecated uses config
173
- ::Guard.reset_evaluator(options) # for initialize_all_templates
174
169
 
175
- # This is messed up (deprecated, etc) and will be fixed later
176
- ::Guard::Guardfile.create_guardfile(abort_on_existence: options[:bare])
170
+ generator = Guardfile::Generator.new(abort_on_existence: options[:bare])
171
+ generator.create_guardfile
177
172
 
178
173
  # Note: this reset "hack" will be fixed after refactoring
179
174
  ::Guard.reset_plugins
180
175
 
181
176
  # Evaluate because it might have existed and creating was skipped
182
- ::Guard.evaluator.evaluate_guardfile
177
+ ::Guard::Guardfile::Evaluator.new(Guard.options).evaluate_guardfile
183
178
 
184
179
  return if options[:bare]
185
180
 
186
181
  if plugin_names.empty?
187
- ::Guard::Guardfile.initialize_all_templates
182
+ generator.initialize_all_templates
188
183
  else
189
184
  plugin_names.each do |plugin_name|
190
- ::Guard::Guardfile.initialize_template(plugin_name)
185
+ generator.initialize_template(plugin_name)
191
186
  end
192
187
  end
193
188
  end