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
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