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,200 @@
|
|
1
|
+
require "lumberjack"
|
2
|
+
|
3
|
+
require "guard/ui"
|
4
|
+
require "guard/watcher"
|
5
|
+
|
6
|
+
module Guard
|
7
|
+
# The runner is responsible for running all methods defined on each plugin.
|
8
|
+
#
|
9
|
+
class Runner
|
10
|
+
# Runs a Guard-task on all registered plugins.
|
11
|
+
#
|
12
|
+
# @param [Symbol] task the task to run
|
13
|
+
#
|
14
|
+
# @param [Hash] scopes either the Guard plugin or the group to run the task
|
15
|
+
# on
|
16
|
+
#
|
17
|
+
# @see self.run_supervised_task
|
18
|
+
#
|
19
|
+
def run(task, scope = {})
|
20
|
+
Lumberjack.unit_of_work do
|
21
|
+
_scoped_plugins(scope) do |guard|
|
22
|
+
run_supervised_task(guard, task) if guard.respond_to?(task)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
MODIFICATION_TASKS = [
|
28
|
+
:run_on_modifications, :run_on_changes, :run_on_change
|
29
|
+
]
|
30
|
+
|
31
|
+
ADDITION_TASKS = [:run_on_additions, :run_on_changes, :run_on_change]
|
32
|
+
REMOVAL_TASKS = [:run_on_removals, :run_on_changes, :run_on_deletion]
|
33
|
+
|
34
|
+
# Runs the appropriate tasks on all registered plugins
|
35
|
+
# based on the passed changes.
|
36
|
+
#
|
37
|
+
# @param [Array<String>] modified the modified paths.
|
38
|
+
# @param [Array<String>] added the added paths.
|
39
|
+
# @param [Array<String>] removed the removed paths.
|
40
|
+
#
|
41
|
+
def run_on_changes(modified, added, removed)
|
42
|
+
types = {
|
43
|
+
MODIFICATION_TASKS => modified,
|
44
|
+
ADDITION_TASKS => added,
|
45
|
+
REMOVAL_TASKS => removed
|
46
|
+
}
|
47
|
+
|
48
|
+
::Guard::UI.clearable
|
49
|
+
|
50
|
+
_scoped_plugins do |guard|
|
51
|
+
::Guard::UI.clear
|
52
|
+
|
53
|
+
types.each do |tasks, unmatched_paths|
|
54
|
+
paths = ::Guard::Watcher.match_files(guard, unmatched_paths)
|
55
|
+
next if paths.empty?
|
56
|
+
|
57
|
+
next unless (task = tasks.detect { |meth| guard.respond_to?(meth) })
|
58
|
+
run_supervised_task(guard, task, paths)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Run a Guard plugin task, but remove the Guard plugin when his work leads
|
64
|
+
# to a system failure.
|
65
|
+
#
|
66
|
+
# When the Group has `:halt_on_fail` disabled, we've to catch
|
67
|
+
# `:task_has_failed` here in order to avoid an uncaught throw error.
|
68
|
+
#
|
69
|
+
# @param [Guard::Plugin] guard the Guard to execute
|
70
|
+
# @param [Symbol] task the task to run
|
71
|
+
# @param [Array] args the arguments for the task
|
72
|
+
# @raise [:task_has_failed] when task has failed
|
73
|
+
#
|
74
|
+
def run_supervised_task(guard, task, *args)
|
75
|
+
catch self.class.stopping_symbol_for(guard) do
|
76
|
+
guard.hook("#{ task }_begin", *args)
|
77
|
+
begin
|
78
|
+
result = guard.send(task, *args)
|
79
|
+
rescue Interrupt
|
80
|
+
throw(:task_has_failed)
|
81
|
+
end
|
82
|
+
guard.hook("#{ task }_end", result)
|
83
|
+
result
|
84
|
+
end
|
85
|
+
rescue ScriptError, StandardError, RuntimeError
|
86
|
+
::Guard::UI.error("#{ guard.class.name } failed to achieve its"\
|
87
|
+
" <#{ task }>, exception was:" \
|
88
|
+
"\n#{ $!.class }: #{ $!.message }" \
|
89
|
+
"\n#{ $!.backtrace.join("\n") }")
|
90
|
+
::Guard.plugins.delete guard
|
91
|
+
::Guard::UI.info("\n#{ guard.class.name } has just been fired")
|
92
|
+
$!
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the symbol that has to be caught when running a supervised task.
|
96
|
+
#
|
97
|
+
# @note If a Guard group is being run and it has the `:halt_on_fail`
|
98
|
+
# option set, this method returns :no_catch as it will be caught at the
|
99
|
+
# group level.
|
100
|
+
# @see ._scoped_plugins
|
101
|
+
#
|
102
|
+
# @param [Guard::Plugin] guard the Guard plugin to execute
|
103
|
+
# @return [Symbol] the symbol to catch
|
104
|
+
#
|
105
|
+
def self.stopping_symbol_for(guard)
|
106
|
+
guard.group.options[:halt_on_fail] ? :no_catch : :task_has_failed
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Loop through all groups and run the given task for each Guard plugin.
|
112
|
+
#
|
113
|
+
# If no scope is supplied, the global Guard scope is taken into account.
|
114
|
+
# If both a plugin and a group scope is given, then only the plugin scope
|
115
|
+
# is used.
|
116
|
+
#
|
117
|
+
# Stop the task run for the all Guard plugins within a group if one Guard
|
118
|
+
# throws `:task_has_failed`.
|
119
|
+
#
|
120
|
+
# @param [Hash] scopes hash with plugins or a groups scope
|
121
|
+
# @yield the task to run
|
122
|
+
#
|
123
|
+
def _scoped_plugins(scopes = {})
|
124
|
+
if plugins = _current_plugins_scope(scopes)
|
125
|
+
plugins.each do |guard|
|
126
|
+
yield(guard)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
_current_groups_scope(scopes).each do |group|
|
130
|
+
current_plugin = nil
|
131
|
+
block_return = catch :task_has_failed do
|
132
|
+
::Guard.plugins(group: group.name).each do |guard|
|
133
|
+
current_plugin = guard
|
134
|
+
yield(guard)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
next unless block_return.nil?
|
139
|
+
|
140
|
+
::Guard::UI.info "#{ current_plugin.class.name } has failed,"\
|
141
|
+
" other group's plugins execution has been halted."
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns the current plugins scope.
|
147
|
+
# Local plugins scope wins over global plugins scope.
|
148
|
+
# If no plugins scope is found, then NO plugins are returned.
|
149
|
+
#
|
150
|
+
# @param [Hash] scopes hash with a local plugins or a groups scope
|
151
|
+
# @return [Array<Guard::Plugin>] the plugins to scope to
|
152
|
+
#
|
153
|
+
def _current_plugins_scope(scope)
|
154
|
+
if plugins = _find_non_empty_plugins_scope(scope)
|
155
|
+
Array(plugins).map do |plugin|
|
156
|
+
plugin.is_a?(Symbol) ? ::Guard.plugin(plugin) : plugin
|
157
|
+
end
|
158
|
+
else
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns the current groups scope.
|
164
|
+
# Local groups scope wins over global groups scope.
|
165
|
+
# If no groups scope is found, then ALL groups are returned.
|
166
|
+
#
|
167
|
+
# @param [Hash] scopes hash with a local plugins or a groups scope
|
168
|
+
# @return [Array<Guard::Group>] the groups to scope to
|
169
|
+
#
|
170
|
+
def _current_groups_scope(scope)
|
171
|
+
Array(_find_non_empty_groups_scope(scope)).map do |group|
|
172
|
+
group.is_a?(Symbol) ? ::Guard.group(group) : group
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Find the first non empty element in the given possibilities
|
177
|
+
#
|
178
|
+
def _find_non_empty_scope(type, local_scope, *additional_possibilities)
|
179
|
+
found = [
|
180
|
+
local_scope[:"#{type}s"],
|
181
|
+
local_scope[type.to_sym],
|
182
|
+
::Guard.scope[:"#{type}s"],
|
183
|
+
additional_possibilities.flatten
|
184
|
+
].compact.detect { |a| !Array(a).empty? }
|
185
|
+
found ? [::Guard.group(:common)] + Array(found) : found
|
186
|
+
end
|
187
|
+
|
188
|
+
# Find the first non empty plugins scope
|
189
|
+
#
|
190
|
+
def _find_non_empty_plugins_scope(scope)
|
191
|
+
_find_non_empty_scope(:plugin, scope)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Find the first non empty groups scope
|
195
|
+
#
|
196
|
+
def _find_non_empty_groups_scope(scope)
|
197
|
+
_find_non_empty_scope(:group, scope, ::Guard.groups)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/lib/guard/setuper.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "thread"
|
2
|
+
require "listen"
|
3
|
+
require "guard/options"
|
4
4
|
|
5
5
|
module Guard
|
6
|
-
|
6
|
+
# Sets up initial variables and options
|
7
7
|
module Setuper
|
8
|
-
|
9
8
|
DEFAULT_OPTIONS = {
|
10
9
|
clear: false,
|
11
10
|
notify: true,
|
@@ -22,12 +21,12 @@ module Guard
|
|
22
21
|
wait_for_delay: nil,
|
23
22
|
listen_on: nil
|
24
23
|
}
|
25
|
-
DEFAULT_GROUPS = [:default]
|
24
|
+
DEFAULT_GROUPS = [:default, :common]
|
26
25
|
|
27
26
|
# Initializes the Guard singleton:
|
28
27
|
#
|
29
28
|
# * Initialize the internal Guard state;
|
30
|
-
# * Create the interactor
|
29
|
+
# * Create the interactor
|
31
30
|
# * Select and initialize the file change listener.
|
32
31
|
#
|
33
32
|
# @option options [Boolean] clear if auto clear the UI should be done
|
@@ -39,59 +38,33 @@ module Guard
|
|
39
38
|
#
|
40
39
|
# @return [Guard] the Guard singleton
|
41
40
|
#
|
41
|
+
|
42
|
+
# TODO: this method has too many instance variables
|
43
|
+
# and some are mock and leak between tests,
|
44
|
+
# so ideally there should be a guard "instance"
|
45
|
+
# object that can be created anew between tests
|
42
46
|
def setup(opts = {})
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
49
|
-
|
50
|
-
if options[:watchdir]
|
51
|
-
# Ensure we have an array
|
52
|
-
@watchdirs = Array(options[:watchdir]).map { |dir| File.expand_path dir }
|
53
|
-
end
|
47
|
+
reset_options(opts)
|
48
|
+
reset_evaluator(opts)
|
49
|
+
|
50
|
+
@queue = Queue.new
|
51
|
+
@runner = ::Guard::Runner.new
|
52
|
+
@watchdirs = _setup_watchdirs
|
54
53
|
|
55
54
|
::Guard::UI.clear(force: true)
|
55
|
+
|
56
56
|
_setup_debug if options[:debug]
|
57
|
-
_setup_listener
|
57
|
+
@listener = _setup_listener
|
58
58
|
_setup_signal_traps
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
reset_scope
|
63
|
-
|
64
|
-
evaluate_guardfile
|
65
|
-
|
66
|
-
setup_scope(groups: options[:group], plugins: options[:plugin])
|
67
|
-
|
68
|
-
_setup_notifier
|
69
|
-
|
60
|
+
_load_guardfile
|
61
|
+
@interactor = _setup_interactor
|
70
62
|
self
|
71
63
|
end
|
72
64
|
|
73
|
-
|
74
|
-
#
|
75
|
-
def options
|
76
|
-
@options ||= ::Guard::Options.new(@opts, DEFAULT_OPTIONS)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Lazy initializer for Guardfile evaluator
|
80
|
-
#
|
81
|
-
def evaluator
|
82
|
-
@evaluator ||= ::Guard::Guardfile::Evaluator.new(@opts || {})
|
83
|
-
end
|
84
|
-
|
85
|
-
# Lazy initializer the interactor unless the user has specified not to.
|
86
|
-
#
|
87
|
-
def interactor
|
88
|
-
return if options[:no_interactions] || !::Guard::Interactor.enabled
|
89
|
-
|
90
|
-
@interactor ||= ::Guard::Interactor.new
|
91
|
-
end
|
65
|
+
attr_reader :options, :evaluator, :interactor
|
92
66
|
|
93
|
-
#
|
94
|
-
#
|
67
|
+
# Used only by tests (for all I know...)
|
95
68
|
def clear_options
|
96
69
|
@options = nil
|
97
70
|
end
|
@@ -117,25 +90,48 @@ module Guard
|
|
117
90
|
# @see Guard.setup_scope
|
118
91
|
#
|
119
92
|
def reset_scope
|
120
|
-
|
93
|
+
# calls Guard.scope=() to set the instance variable directly, as opposed
|
94
|
+
# to Guard.scope()
|
95
|
+
::Guard.scope = { groups: [], plugins: [] }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Used to merge CLI options with Setuper defaults
|
99
|
+
def reset_options(new_options)
|
100
|
+
@options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
|
101
|
+
end
|
102
|
+
|
103
|
+
# TODO: code smell - too many reset_* methods
|
104
|
+
def reset_evaluator(new_options)
|
105
|
+
@evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def save_scope
|
109
|
+
# This actually replaces scope from command line,
|
110
|
+
# so scope set by 'scope' Pry command will be reset
|
111
|
+
@saved_scope = _prepare_scope(::Guard.scope)
|
112
|
+
end
|
113
|
+
|
114
|
+
def restore_scope
|
115
|
+
::Guard.setup_scope(@saved_scope)
|
121
116
|
end
|
122
117
|
|
123
118
|
attr_reader :watchdirs
|
124
119
|
|
125
|
-
# Stores the scopes defined by the user via the `--group` / `-g` option (to
|
126
|
-
# only a specific group) or the `--plugin` / `-P` option (to run only a
|
120
|
+
# Stores the scopes defined by the user via the `--group` / `-g` option (to
|
121
|
+
# run only a specific group) or the `--plugin` / `-P` option (to run only a
|
127
122
|
# specific plugin).
|
128
123
|
#
|
129
124
|
# @see CLI#start
|
130
125
|
# @see Dsl#scope
|
131
126
|
#
|
132
|
-
def setup_scope(
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
127
|
+
def setup_scope(scope = {})
|
128
|
+
# TODO: there should be a special Scope class instead
|
129
|
+
scope = _prepare_scope(scope)
|
130
|
+
{ groups: :add_group, plugins: :plugin }.each do |type, meth|
|
131
|
+
next unless scope[type].any?
|
132
|
+
::Guard.scope[type] = scope[type].map do |item|
|
133
|
+
::Guard.send(meth, item)
|
134
|
+
end
|
139
135
|
end
|
140
136
|
end
|
141
137
|
|
@@ -146,17 +142,40 @@ module Guard
|
|
146
142
|
#
|
147
143
|
def evaluate_guardfile
|
148
144
|
evaluator.evaluate_guardfile
|
149
|
-
|
145
|
+
msg = "No plugins found in Guardfile, please add at least one."
|
146
|
+
::Guard::UI.error msg unless _non_builtin_plugins?
|
150
147
|
end
|
151
148
|
|
152
|
-
|
149
|
+
# Asynchronously trigger changes
|
150
|
+
#
|
151
|
+
# Currently supported args:
|
152
|
+
#
|
153
|
+
# old style hash: {modified: ['foo'], added: ['bar'], removed: []}
|
154
|
+
#
|
155
|
+
# new style signals with args: [:guard_pause, :unpaused ]
|
156
|
+
#
|
157
|
+
def async_queue_add(changes)
|
158
|
+
@queue << changes
|
153
159
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
@interactor = nil
|
160
|
+
# Putting interactor in background puts guard into foreground
|
161
|
+
# so it can handle change notifications
|
162
|
+
Thread.new { interactor.background }
|
158
163
|
end
|
159
164
|
|
165
|
+
def pending_changes?
|
166
|
+
! @queue.empty?
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_builtin_plugins(guardfile)
|
170
|
+
return unless guardfile
|
171
|
+
|
172
|
+
pattern = _relative_pathname(guardfile).to_s
|
173
|
+
watcher = ::Guard::Watcher.new(pattern)
|
174
|
+
::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
160
179
|
# Sets up various debug behaviors:
|
161
180
|
#
|
162
181
|
# * Abort threads on exception;
|
@@ -175,15 +194,31 @@ module Guard
|
|
175
194
|
#
|
176
195
|
def _setup_listener
|
177
196
|
if options[:listen_on]
|
178
|
-
|
197
|
+
Listen.on(options[:listen_on], &_listener_callback)
|
179
198
|
else
|
180
199
|
listener_options = {}
|
181
200
|
[:latency, :force_polling, :wait_for_delay].each do |option|
|
182
201
|
listener_options[option] = options[option] if options[option]
|
183
202
|
end
|
184
203
|
listen_args = watchdirs + [listener_options]
|
185
|
-
|
204
|
+
Listen.to(*listen_args, &_listener_callback)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Process the change queue, running tasks within the main Guard thread
|
209
|
+
def _process_queue
|
210
|
+
actions, changes = [], { modified: [], added: [], removed: [] }
|
211
|
+
|
212
|
+
while pending_changes?
|
213
|
+
if (item = @queue.pop).first.is_a?(Symbol)
|
214
|
+
actions << item
|
215
|
+
else
|
216
|
+
item.each { |key, value| changes[key] += value }
|
217
|
+
end
|
186
218
|
end
|
219
|
+
|
220
|
+
_run_actions(actions)
|
221
|
+
runner.run_on_changes(*changes.values)
|
187
222
|
end
|
188
223
|
|
189
224
|
# Sets up traps to catch signals used to control Guard.
|
@@ -194,39 +229,24 @@ module Guard
|
|
194
229
|
# - 'INT' which is delegated to Pry if active, otherwise stops Guard.
|
195
230
|
#
|
196
231
|
def _setup_signal_traps
|
197
|
-
|
198
|
-
if Signal.list.keys.include?('USR1')
|
199
|
-
Signal.trap('USR1') do
|
200
|
-
unless listener.paused?
|
201
|
-
Thread.new { within_preserved_state { ::Guard.pause } }
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
232
|
+
return if defined?(JRUBY_VERSION)
|
205
233
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
Thread.new { within_preserved_state { ::Guard.pause } }
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
234
|
+
if Signal.list.keys.include?("USR1")
|
235
|
+
Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
|
236
|
+
end
|
213
237
|
|
214
|
-
|
215
|
-
|
216
|
-
if interactor && interactor.thread
|
217
|
-
interactor.thread.raise(Interrupt)
|
218
|
-
else
|
219
|
-
::Guard.stop
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
238
|
+
if Signal.list.keys.include?("USR2")
|
239
|
+
Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
|
223
240
|
end
|
241
|
+
|
242
|
+
return unless Signal.list.keys.include?("INT")
|
243
|
+
Signal.trap("INT") { interactor.handle_interrupt }
|
224
244
|
end
|
225
245
|
|
226
246
|
# Enables or disables the notifier based on user's configurations.
|
227
247
|
#
|
228
248
|
def _setup_notifier
|
229
|
-
if options[:notify] && ENV[
|
249
|
+
if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
|
230
250
|
::Guard::Notifier.turn_on
|
231
251
|
else
|
232
252
|
::Guard::Notifier.turn_off
|
@@ -239,7 +259,7 @@ module Guard
|
|
239
259
|
def _debug_command_execution
|
240
260
|
Kernel.send(:alias_method, :original_system, :system)
|
241
261
|
Kernel.send(:define_method, :system) do |command, *args|
|
242
|
-
::Guard::UI.debug "Command execution: #{ command } #{ args.join(
|
262
|
+
::Guard::UI.debug "Command execution: #{ command } #{ args.join(" ") }"
|
243
263
|
Kernel.send :original_system, command, *args
|
244
264
|
end
|
245
265
|
|
@@ -250,53 +270,102 @@ module Guard
|
|
250
270
|
end
|
251
271
|
end
|
252
272
|
|
273
|
+
# TODO: Guard::Watch or Guard::Scope should provide this
|
274
|
+
def _scoped_watchers
|
275
|
+
watchers = []
|
276
|
+
runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
|
277
|
+
watchers
|
278
|
+
end
|
279
|
+
|
253
280
|
# Check if any of the changes are actually watched for
|
254
|
-
#
|
255
|
-
# NOTE: this is called from the listen thread - be careful to not
|
256
|
-
# modify any state
|
257
|
-
#
|
258
|
-
# TODO: move this to watcher class?
|
259
|
-
#
|
260
281
|
def _relevant_changes?(changes)
|
261
|
-
|
262
|
-
|
282
|
+
files = changes.values.flatten(1)
|
283
|
+
watchers = _scoped_watchers
|
284
|
+
watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
|
285
|
+
end
|
263
286
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
287
|
+
def _relative_pathname(path)
|
288
|
+
full_path = Pathname(path)
|
289
|
+
full_path.relative_path_from(Pathname.pwd)
|
290
|
+
rescue ArgumentError
|
291
|
+
full_path
|
292
|
+
end
|
293
|
+
|
294
|
+
def _relative_pathnames(paths)
|
295
|
+
paths.map { |path| _relative_pathname(path) }
|
296
|
+
end
|
297
|
+
|
298
|
+
def _run_actions(actions)
|
299
|
+
actions.each do |action_args|
|
300
|
+
args = action_args.dup
|
301
|
+
namespaced_action = args.shift
|
302
|
+
action = namespaced_action.to_s.sub(/^guard_/, "")
|
303
|
+
if ::Guard.respond_to?(action)
|
304
|
+
::Guard.send(action, *args)
|
305
|
+
else
|
306
|
+
fail "Unknown action: #{action.inspect}"
|
281
307
|
end
|
282
308
|
end
|
283
309
|
end
|
284
310
|
|
311
|
+
def _setup_watchdirs
|
312
|
+
dirs = Array(options[:watchdir])
|
313
|
+
dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
|
314
|
+
end
|
315
|
+
|
285
316
|
def _listener_callback
|
286
317
|
lambda do |modified, added, removed|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
_relative_paths(all_changes)
|
318
|
+
relative_paths = {
|
319
|
+
modified: _relative_pathnames(modified),
|
320
|
+
added: _relative_pathnames(added),
|
321
|
+
removed: _relative_pathnames(removed)
|
322
|
+
}
|
293
323
|
|
294
|
-
if _relevant_changes?(
|
295
|
-
within_preserved_state do
|
296
|
-
runner.run_on_changes(*all_changes.values)
|
297
|
-
end
|
298
|
-
end
|
324
|
+
async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
|
299
325
|
end
|
300
326
|
end
|
327
|
+
|
328
|
+
def _reset_all
|
329
|
+
reset_groups
|
330
|
+
reset_plugins
|
331
|
+
reset_scope
|
332
|
+
end
|
333
|
+
|
334
|
+
def _setup_interactor
|
335
|
+
::Guard::Interactor.new(options[:no_interactions])
|
336
|
+
end
|
337
|
+
|
338
|
+
def _load_guardfile
|
339
|
+
_reset_all
|
340
|
+
evaluate_guardfile
|
341
|
+
setup_scope
|
342
|
+
_setup_notifier
|
343
|
+
end
|
344
|
+
|
345
|
+
def _prepare_scope(scope)
|
346
|
+
plugins = Array(options[:plugin])
|
347
|
+
plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
|
348
|
+
|
349
|
+
groups = Array(options[:group])
|
350
|
+
groups = Array(scope[:groups] || scope[:group]) if groups.empty?
|
351
|
+
|
352
|
+
{ plugins: plugins, groups: groups }
|
353
|
+
end
|
354
|
+
|
355
|
+
def _non_builtin_plugins?
|
356
|
+
plugins.map(&:name) != ["reevaluator"]
|
357
|
+
end
|
358
|
+
|
359
|
+
def _reset_for_tests
|
360
|
+
@options = nil
|
361
|
+
@queue = nil
|
362
|
+
@runner = nil
|
363
|
+
@evaluator = nil
|
364
|
+
@watchdirs = nil
|
365
|
+
@watchdirs = nil
|
366
|
+
@listener = nil
|
367
|
+
@interactor = nil
|
368
|
+
::Guard.scope = nil
|
369
|
+
end
|
301
370
|
end
|
302
371
|
end
|