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.
- checksums.yaml +4 -4
- data/README.md +73 -58
- data/bin/guard +2 -2
- data/lib/guard.rb +64 -59
- data/lib/guard/cli.rb +66 -60
- data/lib/guard/cli.rb.orig +215 -0
- data/lib/guard/commander.rb +45 -69
- data/lib/guard/commands/all.rb +21 -19
- data/lib/guard/commands/change.rb +17 -22
- data/lib/guard/commands/notification.rb +15 -16
- data/lib/guard/commands/pause.rb +14 -15
- data/lib/guard/commands/reload.rb +19 -20
- data/lib/guard/commands/scope.rb +23 -19
- data/lib/guard/commands/show.rb +13 -16
- data/lib/guard/deprecated_methods.rb +6 -10
- data/lib/guard/deprecator.rb +52 -37
- data/lib/guard/dsl.rb +55 -33
- data/lib/guard/dsl_describer.rb +83 -31
- data/lib/guard/dsl_describer.rb.orig +184 -0
- data/lib/guard/group.rb +7 -6
- data/lib/guard/guard.rb +4 -4
- data/lib/guard/guard.rb.orig +42 -0
- data/lib/guard/guardfile.rb +12 -13
- data/lib/guard/guardfile/evaluator.rb +77 -55
- data/lib/guard/guardfile/evaluator.rb.orig +275 -0
- data/lib/guard/guardfile/generator.rb +25 -20
- data/lib/guard/interactor.rb +52 -293
- data/lib/guard/interactor.rb.orig +85 -0
- data/lib/guard/jobs/base.rb +21 -0
- data/lib/guard/jobs/pry_wrapper.rb +290 -0
- data/lib/guard/jobs/pry_wrapper.rb.orig +293 -0
- data/lib/guard/jobs/sleep.rb +25 -0
- data/lib/guard/notifier.rb +42 -39
- data/lib/guard/notifiers/base.rb +25 -24
- data/lib/guard/notifiers/emacs.rb +30 -24
- data/lib/guard/notifiers/file_notifier.rb +3 -7
- data/lib/guard/notifiers/gntp.rb +22 -22
- data/lib/guard/notifiers/growl.rb +16 -15
- data/lib/guard/notifiers/libnotify.rb +7 -10
- data/lib/guard/notifiers/notifysend.rb +15 -14
- data/lib/guard/notifiers/rb_notifu.rb +8 -10
- data/lib/guard/notifiers/terminal_notifier.rb +15 -11
- data/lib/guard/notifiers/terminal_title.rb +4 -8
- data/lib/guard/notifiers/tmux.rb +104 -71
- data/lib/guard/options.rb +1 -5
- data/lib/guard/plugin.rb +1 -3
- data/lib/guard/plugin/base.rb +12 -9
- data/lib/guard/plugin/hooker.rb +1 -5
- data/lib/guard/plugin_util.rb +46 -25
- data/lib/guard/plugin_util.rb.orig +178 -0
- data/lib/guard/rake_task.rb +4 -7
- data/lib/guard/reevaluator.rb +13 -0
- data/lib/guard/runner.rb +50 -78
- data/lib/guard/runner.rb.orig +200 -0
- data/lib/guard/setuper.rb +199 -130
- data/lib/guard/setuper.rb.orig +348 -0
- data/lib/guard/sheller.rb +107 -0
- data/lib/guard/tags +367 -0
- data/lib/guard/ui.rb +50 -38
- data/lib/guard/ui.rb.orig +254 -0
- data/lib/guard/ui/colors.rb +17 -21
- data/lib/guard/version.rb +1 -1
- data/lib/guard/version.rb.orig +3 -0
- data/lib/guard/watcher.rb +49 -62
- metadata +21 -4
- 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
|