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