guard 2.6.1 → 2.7.0

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