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