guard 2.8.2 → 2.9.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 +0 -7
- data/lib/guard.rb +220 -152
- data/lib/guard.rb.orig +213 -155
- data/lib/guard/aruba_adapter.rb +2 -2
- data/lib/guard/cli.rb +8 -13
- data/lib/guard/cli.rb.orig +12 -10
- data/lib/guard/commander.rb +15 -7
- data/lib/guard/commands/all.rb +3 -0
- data/lib/guard/commands/change.rb +3 -0
- data/lib/guard/commands/pause.rb +2 -0
- data/lib/guard/commands/reload.rb +4 -0
- data/lib/guard/commands/scope.rb +3 -0
- data/lib/guard/config.rb +24 -0
- data/lib/guard/deprecated/dsl.rb +45 -0
- data/lib/guard/deprecated/guard.rb +166 -0
- data/lib/guard/deprecated/guardfile.rb +84 -0
- data/lib/guard/dsl.rb +24 -13
- data/lib/guard/dsl.rb.orig +378 -0
- data/lib/guard/dsl_describer.rb +8 -2
- data/lib/guard/dsl_describer.rb.orig +11 -3
- data/lib/guard/guardfile.rb +32 -44
- data/lib/guard/guardfile/evaluator.rb +13 -6
- data/lib/guard/guardfile/generator.rb +4 -3
- data/lib/guard/interactor.rb +7 -3
- data/lib/guard/internals/debugging.rb +1 -0
- data/lib/guard/internals/environment.rb +93 -0
- data/lib/guard/internals/helpers.rb +13 -0
- data/lib/guard/internals/traps.rb +10 -0
- data/lib/guard/jobs/pry_wrapper.rb +4 -3
- data/lib/guard/jobs/sleep.rb +2 -0
- data/lib/guard/metadata.rb +190 -0
- data/lib/guard/notifier.rb +124 -99
- data/lib/guard/notifier.rb.orig +124 -99
- data/lib/guard/notifier/detected.rb +83 -0
- data/lib/guard/notifiers/emacs.rb +2 -1
- data/lib/guard/notifiers/tmux.rb +173 -177
- data/lib/guard/plugin/base.rb +2 -0
- data/lib/guard/plugin_util.rb +26 -32
- data/lib/guard/reevaluator.rb +3 -3
- data/lib/guard/reevaluator.rb.orig +22 -0
- data/lib/guard/runner.rb +1 -0
- data/lib/guard/session.rb +5 -0
- data/lib/guard/sheller.rb +2 -2
- data/lib/guard/templates/Guardfile +4 -0
- data/lib/guard/templates/Guardfile.orig +2 -0
- data/lib/guard/terminal.rb +1 -0
- data/lib/guard/ui.rb +4 -1
- data/lib/guard/version.rb +1 -1
- data/lib/guard/version.rb.orig +1 -1
- data/lib/guard/watcher.rb +3 -1
- data/lib/guard/watcher.rb.orig +122 -0
- data/man/guard.1 +1 -4
- data/man/guard.1.html +1 -4
- metadata +17 -25
- data/lib/guard/commander.rb.orig +0 -103
- data/lib/guard/commands/all.rb.orig +0 -36
- data/lib/guard/commands/reload.rb.orig +0 -34
- data/lib/guard/commands/scope.rb.orig +0 -36
- data/lib/guard/deprecated_methods.rb +0 -72
- data/lib/guard/deprecated_methods.rb.orig +0 -71
- data/lib/guard/deprecator.rb +0 -133
- data/lib/guard/deprecator.rb.orig +0 -206
- data/lib/guard/guard.rb +0 -100
- data/lib/guard/guard.rb.orig +0 -42
- data/lib/guard/guardfile.rb.orig +0 -43
- data/lib/guard/guardfile/evaluator.rb.orig +0 -275
- data/lib/guard/internals/debugging.rb.orig +0 -0
- data/lib/guard/internals/environment.rb.orig +0 -0
- data/lib/guard/internals/tracing.rb.orig +0 -0
- data/lib/guard/notifiers/base.rb.orig +0 -221
- data/lib/guard/notifiers/tmux.rb.orig +0 -339
- data/lib/guard/plugin_util.rb.orig +0 -186
- data/lib/guard/runner.rb.orig +0 -210
- data/lib/guard/setuper.rb +0 -359
- data/lib/guard/setuper.rb.orig +0 -395
- data/lib/guard/ui.rb.orig +0 -278
@@ -1,186 +0,0 @@
|
|
1
|
-
require "guard/ui"
|
2
|
-
|
3
|
-
module Guard
|
4
|
-
# This class contains useful methods to:
|
5
|
-
#
|
6
|
-
# * Fetch all the Guard plugins names;
|
7
|
-
# * Initialize a plugin, get its location;
|
8
|
-
# * Return its class name;
|
9
|
-
# * Add its template to the Guardfile.
|
10
|
-
#
|
11
|
-
class PluginUtil
|
12
|
-
ERROR_NO_GUARD_OR_CLASS = "Could not load 'guard/%s' or'\
|
13
|
-
' find class Guard::%s"
|
14
|
-
|
15
|
-
INFO_ADDED_GUARD_TO_GUARDFILE = "%s guard added to Guardfile,"\
|
16
|
-
" feel free to edit it"
|
17
|
-
|
18
|
-
attr_accessor :name
|
19
|
-
|
20
|
-
# Returns a list of Guard plugin Gem names installed locally.
|
21
|
-
#
|
22
|
-
# @return [Array<String>] a list of Guard plugin gem names
|
23
|
-
#
|
24
|
-
def self.plugin_names
|
25
|
-
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create("1.8.0")
|
26
|
-
Gem::Specification.find_all.select do |x|
|
27
|
-
if x.name =~ /^guard-/
|
28
|
-
true
|
29
|
-
elsif x.name != "guard"
|
30
|
-
|
31
|
-
guard_plugin_path = File.join(
|
32
|
-
x.full_gem_path,
|
33
|
-
"lib/guard/#{ x.name }.rb"
|
34
|
-
)
|
35
|
-
|
36
|
-
File.exist?(guard_plugin_path)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
else
|
40
|
-
::Guard::UI.deprecation \
|
41
|
-
"Rubygems version prior to 1.8.0 are no longer supported"\
|
42
|
-
" and may not work"
|
43
|
-
|
44
|
-
Gem.source_index.find_name(/^guard-/)
|
45
|
-
end.map { |x| x.name.sub(/^guard-/, "") }.uniq
|
46
|
-
end
|
47
|
-
|
48
|
-
# Initializes a new `Guard::PluginUtil` object.
|
49
|
-
#
|
50
|
-
# @param [String] name the name of the Guard plugin
|
51
|
-
#
|
52
|
-
def initialize(name)
|
53
|
-
@name = name.to_s.sub(/^guard-/, "")
|
54
|
-
end
|
55
|
-
|
56
|
-
# Initializes a new `Guard::Plugin` with the given `options` hash. This
|
57
|
-
# methods handles plugins that inherit from the deprecated `Guard::Guard`
|
58
|
-
# class as well as plugins that inherit from `Guard::Plugin`.
|
59
|
-
#
|
60
|
-
# @see Guard::Plugin
|
61
|
-
# @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
|
62
|
-
# upgrade for Guard 2.0
|
63
|
-
#
|
64
|
-
# @return [Guard::Plugin] the initialized plugin
|
65
|
-
# @return [Guard::Guard] the initialized plugin. This return type is
|
66
|
-
# deprecated and the plugin's maintainer should update it to be
|
67
|
-
# compatible with Guard 2.0. For more information on how to upgrade for
|
68
|
-
# Guard 2.0, please head over to:
|
69
|
-
# https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0
|
70
|
-
#
|
71
|
-
def initialize_plugin(options)
|
72
|
-
klass = plugin_class
|
73
|
-
fail "Could not load class: #{_constant_name.inspect}" unless klass
|
74
|
-
if klass.superclass.to_s == "Guard::Guard"
|
75
|
-
klass.new(options.delete(:watchers), options)
|
76
|
-
else
|
77
|
-
begin
|
78
|
-
klass.new(options)
|
79
|
-
rescue ArgumentError => e
|
80
|
-
fail "Failed to call #{klass}.new(options): #{e}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Locates a path to a Guard plugin gem.
|
86
|
-
#
|
87
|
-
# @return [String] the full path to the plugin gem
|
88
|
-
#
|
89
|
-
def plugin_location
|
90
|
-
@plugin_location ||= begin
|
91
|
-
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create("1.8.0")
|
92
|
-
Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
|
93
|
-
else
|
94
|
-
Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
|
95
|
-
end
|
96
|
-
end
|
97
|
-
rescue
|
98
|
-
::Guard::UI.error "Could not find 'guard-#{ name }' gem path."
|
99
|
-
end
|
100
|
-
|
101
|
-
# Tries to load the Guard plugin main class. This transforms the supplied
|
102
|
-
# plugin name into a class name:
|
103
|
-
#
|
104
|
-
# * `guardname` will become `Guard::Guardname`
|
105
|
-
# * `dashed-guard-name` will become `Guard::DashedGuardName`
|
106
|
-
# * `underscore_guard_name` will become `Guard::UnderscoreGuardName`
|
107
|
-
#
|
108
|
-
# When no class is found with the strict case sensitive rules, another
|
109
|
-
# try is made to locate the class without matching case:
|
110
|
-
#
|
111
|
-
# * `rspec` will find a class `Guard::RSpec`
|
112
|
-
#
|
113
|
-
# @option options [Boolean] fail_gracefully whether error messages should
|
114
|
-
# not be printed
|
115
|
-
#
|
116
|
-
# @return [Class, nil] the loaded class
|
117
|
-
#
|
118
|
-
def plugin_class(options = {})
|
119
|
-
options = { fail_gracefully: false }.merge(options)
|
120
|
-
|
121
|
-
try_require = false
|
122
|
-
begin
|
123
|
-
require "guard/#{ name.downcase }" if try_require
|
124
|
-
|
125
|
-
@plugin_class ||= ::Guard.const_get(_plugin_constant)
|
126
|
-
rescue TypeError => error
|
127
|
-
if try_require
|
128
|
-
::Guard::UI.error "Could not find class Guard::#{ _constant_name }"
|
129
|
-
::Guard::UI.error error.backtrace.join("\n")
|
130
|
-
else
|
131
|
-
try_require = true
|
132
|
-
retry
|
133
|
-
end
|
134
|
-
rescue LoadError => error
|
135
|
-
unless options[:fail_gracefully]
|
136
|
-
UI.error ERROR_NO_GUARD_OR_CLASS % [name.downcase, _constant_name]
|
137
|
-
UI.error error.backtrace.join("\n")
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Adds a plugin's template to the Guardfile.
|
143
|
-
#
|
144
|
-
def add_to_guardfile
|
145
|
-
msg = "Guard.evaluator not initialized"
|
146
|
-
fail msg if ::Guard.evaluator.nil?
|
147
|
-
if ::Guard.evaluator.guardfile_include?(name)
|
148
|
-
::Guard::UI.info "Guardfile already includes #{ name } guard"
|
149
|
-
else
|
150
|
-
content = File.read("Guardfile")
|
151
|
-
File.open("Guardfile", "wb") do |f|
|
152
|
-
f.puts(content)
|
153
|
-
f.puts("")
|
154
|
-
f.puts(plugin_class.template(plugin_location))
|
155
|
-
end
|
156
|
-
|
157
|
-
UI.info INFO_ADDED_GUARD_TO_GUARDFILE % name
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
private
|
162
|
-
|
163
|
-
# Returns the constant for the current plugin.
|
164
|
-
#
|
165
|
-
# @example Returns the constant for a plugin
|
166
|
-
# > Guard::PluginUtil.new('rspec').send(:_plugin_constant)
|
167
|
-
# => Guard::RSpec
|
168
|
-
#
|
169
|
-
def _plugin_constant
|
170
|
-
@_plugin_constant ||= ::Guard.constants.detect do |c|
|
171
|
-
c.to_s.downcase == _constant_name.downcase
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# Guesses the most probable name for the current plugin based on its name.
|
176
|
-
#
|
177
|
-
# @example Returns the most probable name for a plugin
|
178
|
-
# > Guard::PluginUtil.new('rspec').send(:_constant_name)
|
179
|
-
# => "Rspec"
|
180
|
-
#
|
181
|
-
def _constant_name
|
182
|
-
@_constant_name ||= name.gsub(/\/(.?)/) { "::#{ $1.upcase }" }.
|
183
|
-
gsub(/(?:^|[_-])(.)/) { $1.upcase }
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
data/lib/guard/runner.rb.orig
DELETED
@@ -1,210 +0,0 @@
|
|
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
|
-
def run(task, scope_hash = {})
|
18
|
-
Lumberjack.unit_of_work do
|
19
|
-
_scoped_plugins(scope_hash || {}) do |plugin|
|
20
|
-
_supervise(plugin, task) if plugin.respond_to?(task)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
MODIFICATION_TASKS = [
|
26
|
-
:run_on_modifications, :run_on_changes, :run_on_change
|
27
|
-
]
|
28
|
-
|
29
|
-
ADDITION_TASKS = [:run_on_additions, :run_on_changes, :run_on_change]
|
30
|
-
REMOVAL_TASKS = [:run_on_removals, :run_on_changes, :run_on_deletion]
|
31
|
-
|
32
|
-
# Runs the appropriate tasks on all registered plugins
|
33
|
-
# based on the passed changes.
|
34
|
-
#
|
35
|
-
# @param [Array<String>] modified the modified paths.
|
36
|
-
# @param [Array<String>] added the added paths.
|
37
|
-
# @param [Array<String>] removed the removed paths.
|
38
|
-
#
|
39
|
-
def run_on_changes(modified, added, removed)
|
40
|
-
types = {
|
41
|
-
MODIFICATION_TASKS => modified,
|
42
|
-
ADDITION_TASKS => added,
|
43
|
-
REMOVAL_TASKS => removed
|
44
|
-
}
|
45
|
-
|
46
|
-
::Guard::UI.clearable
|
47
|
-
|
48
|
-
_scoped_plugins do |plugin|
|
49
|
-
::Guard::UI.clear
|
50
|
-
|
51
|
-
types.each do |tasks, unmatched_paths|
|
52
|
-
next if unmatched_paths.empty?
|
53
|
-
match_result = ::Guard::Watcher.match_files(plugin, unmatched_paths)
|
54
|
-
next if match_result.empty?
|
55
|
-
|
56
|
-
next unless (task = tasks.detect { |meth| plugin.respond_to?(meth) })
|
57
|
-
_supervise(plugin, task, match_result)
|
58
|
-
::Guard::UI.clearable
|
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 _supervise(plugin, task, *args)
|
75
|
-
catch self.class.stopping_symbol_for(plugin) do
|
76
|
-
plugin.hook("#{ task }_begin", *args)
|
77
|
-
begin
|
78
|
-
result = plugin.send(task, *args)
|
79
|
-
rescue Interrupt
|
80
|
-
throw(:task_has_failed)
|
81
|
-
end
|
82
|
-
plugin.hook("#{ task }_end", result)
|
83
|
-
result
|
84
|
-
end
|
85
|
-
rescue ScriptError, StandardError, RuntimeError
|
86
|
-
::Guard::UI.error("#{ plugin.class.name } failed to achieve its"\
|
87
|
-
" <#{ task }>, exception was:" \
|
88
|
-
"\n#{ $!.class }: #{ $!.message }" \
|
89
|
-
"\n#{ $!.backtrace.join("\n") }")
|
90
|
-
::Guard.remove_plugin(plugin)
|
91
|
-
::Guard::UI.info("\n#{ plugin.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
|
-
fail "NO PLUGIN SCOPE" if scopes.nil?
|
125
|
-
if plugins = _current_plugins_scope(scopes)
|
126
|
-
plugins.each do |guard|
|
127
|
-
yield(guard)
|
128
|
-
end
|
129
|
-
else
|
130
|
-
_current_groups_scope(scopes).each do |group|
|
131
|
-
fail "Invalid group: #{group.inspect}" unless group.respond_to?(:name)
|
132
|
-
current_plugin = nil
|
133
|
-
block_return = catch :task_has_failed do
|
134
|
-
::Guard.plugins(group: group.name).each do |guard|
|
135
|
-
current_plugin = guard
|
136
|
-
yield(guard)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
next unless block_return.nil?
|
141
|
-
|
142
|
-
::Guard::UI.info "#{ current_plugin.class.name } has failed,"\
|
143
|
-
" other group's plugins execution has been halted."
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Returns the current plugins scope.
|
149
|
-
# Local plugins scope wins over global plugins scope.
|
150
|
-
# If no plugins scope is found, then NO plugins are returned.
|
151
|
-
#
|
152
|
-
# @param [Hash] scopes hash with a local plugins or a groups scope
|
153
|
-
# @return [Array<Guard::Plugin>] the plugins to scope to
|
154
|
-
#
|
155
|
-
def _current_plugins_scope(scope)
|
156
|
-
return nil unless (plugins = _find_non_empty_plugins_scope(scope))
|
157
|
-
|
158
|
-
Array(plugins).map { |plugin| _instantiate(:plugin, plugin) }
|
159
|
-
end
|
160
|
-
|
161
|
-
# Returns the current groups scope.
|
162
|
-
# Local groups scope wins over global groups scope.
|
163
|
-
# If no groups scope is found, then ALL groups are returned.
|
164
|
-
#
|
165
|
-
# @param [Hash] scopes hash with a local plugins or a groups scope
|
166
|
-
# @return [Array<Guard::Group>] the groups to scope to
|
167
|
-
#
|
168
|
-
def _current_groups_scope(scope)
|
169
|
-
<<<<<<< HEAD
|
170
|
-
found = _find_non_empty_groups_scope(scope)
|
171
|
-
groups = Array(found).map { |group| _instantiate(:group, group) }
|
172
|
-
return groups if groups.any? { |g| g.name == :common }
|
173
|
-
([_instantiate(:group, :common)] + Array(found)).compact
|
174
|
-
=======
|
175
|
-
groups = _find_non_empty_groups_scope(scope)
|
176
|
-
Array(groups).map { |group| _instantiate(:group, group) }
|
177
|
-
>>>>>>> origin/api_safe_refactoring
|
178
|
-
end
|
179
|
-
|
180
|
-
def _instantiate(meth, obj)
|
181
|
-
(obj.is_a?(Symbol) || obj.is_a?(String)) ? ::Guard.send(meth, obj) : obj
|
182
|
-
end
|
183
|
-
|
184
|
-
# Find the first non empty element in the given possibilities
|
185
|
-
#
|
186
|
-
def _find_non_empty_scope(type, local_scope, *additional_possibilities)
|
187
|
-
found = [
|
188
|
-
local_scope[:"#{type}s"],
|
189
|
-
[local_scope[type.to_sym]],
|
190
|
-
::Guard.scope[:"#{type}s"],
|
191
|
-
additional_possibilities.flatten
|
192
|
-
]
|
193
|
-
found.compact.detect { |a| !Array(a).compact.empty? }
|
194
|
-
end
|
195
|
-
|
196
|
-
# Find the first non empty plugins scope
|
197
|
-
#
|
198
|
-
def _find_non_empty_plugins_scope(scope)
|
199
|
-
fail "NO PLUGIN SCOPE" if scope.nil?
|
200
|
-
_find_non_empty_scope(:plugin, scope)
|
201
|
-
end
|
202
|
-
|
203
|
-
# Find the first non empty groups scope
|
204
|
-
#
|
205
|
-
def _find_non_empty_groups_scope(scope)
|
206
|
-
common = [::Guard.group(:common)]
|
207
|
-
common + _find_non_empty_scope(:group, scope, ::Guard.groups)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
data/lib/guard/setuper.rb
DELETED
@@ -1,359 +0,0 @@
|
|
1
|
-
require "thread"
|
2
|
-
require "listen"
|
3
|
-
require "guard/options"
|
4
|
-
|
5
|
-
require "guard/internals/debugging"
|
6
|
-
|
7
|
-
module Guard
|
8
|
-
# Sets up initial variables and options
|
9
|
-
module Setuper
|
10
|
-
DEFAULT_OPTIONS = {
|
11
|
-
clear: false,
|
12
|
-
notify: true,
|
13
|
-
debug: false,
|
14
|
-
group: [],
|
15
|
-
plugin: [],
|
16
|
-
watchdir: nil,
|
17
|
-
guardfile: nil,
|
18
|
-
no_interactions: false,
|
19
|
-
no_bundler_warning: false,
|
20
|
-
latency: nil,
|
21
|
-
force_polling: false,
|
22
|
-
wait_for_delay: nil,
|
23
|
-
listen_on: nil
|
24
|
-
}
|
25
|
-
DEFAULT_GROUPS = [:common, :default]
|
26
|
-
|
27
|
-
# Initializes the Guard singleton:
|
28
|
-
#
|
29
|
-
# * Initialize the internal Guard state;
|
30
|
-
# * Create the interactor
|
31
|
-
# * Select and initialize the file change listener.
|
32
|
-
#
|
33
|
-
# @option options [Boolean] clear if auto clear the UI should be done
|
34
|
-
# @option options [Boolean] notify if system notifications should be shown
|
35
|
-
# @option options [Boolean] debug if debug output should be shown
|
36
|
-
# @option options [Array<String>] group the list of groups to start
|
37
|
-
# @option options [Array<String>] watchdir the directories to watch
|
38
|
-
# @option options [String] guardfile the path to the Guardfile
|
39
|
-
#
|
40
|
-
# @return [Guard] the Guard singleton
|
41
|
-
#
|
42
|
-
|
43
|
-
# TODO: this method has too many instance variables
|
44
|
-
# and some are mock and leak between tests,
|
45
|
-
# so ideally there should be a guard "instance"
|
46
|
-
# object that can be created anew between tests
|
47
|
-
def setup(opts = {})
|
48
|
-
# NOTE: must be set before anything calls Guard.options
|
49
|
-
reset_options(opts)
|
50
|
-
|
51
|
-
# NOTE: must be set before anything calls Guard::UI.debug
|
52
|
-
::Guard::Internals::Debugging.start if options[:debug]
|
53
|
-
|
54
|
-
reset_evaluator(opts)
|
55
|
-
|
56
|
-
@queue = Queue.new
|
57
|
-
@runner = ::Guard::Runner.new
|
58
|
-
@watchdirs = _setup_watchdirs
|
59
|
-
|
60
|
-
::Guard::UI.reset_and_clear
|
61
|
-
|
62
|
-
@listener = _setup_listener
|
63
|
-
_setup_signal_traps
|
64
|
-
|
65
|
-
_load_guardfile
|
66
|
-
@interactor = _setup_interactor
|
67
|
-
self
|
68
|
-
end
|
69
|
-
|
70
|
-
attr_reader :options, :interactor
|
71
|
-
|
72
|
-
# Used only by tests (for all I know...)
|
73
|
-
def clear_options
|
74
|
-
@options = nil
|
75
|
-
end
|
76
|
-
|
77
|
-
# Initializes the groups array with the default group(s).
|
78
|
-
#
|
79
|
-
# @see DEFAULT_GROUPS
|
80
|
-
#
|
81
|
-
def reset_groups
|
82
|
-
@groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
|
83
|
-
end
|
84
|
-
|
85
|
-
# Initializes the plugins array to an empty array.
|
86
|
-
#
|
87
|
-
# @see Guard.plugins
|
88
|
-
#
|
89
|
-
def reset_plugins
|
90
|
-
@plugins = []
|
91
|
-
end
|
92
|
-
|
93
|
-
# Initializes the scope hash to `{ groups: [], plugins: [] }`.
|
94
|
-
#
|
95
|
-
# @see Guard.setup_scope
|
96
|
-
#
|
97
|
-
def reset_scope
|
98
|
-
# calls Guard.scope=() to set the instance variable directly, as opposed
|
99
|
-
# to Guard.scope()
|
100
|
-
::Guard.scope = { groups: [], plugins: [] }
|
101
|
-
end
|
102
|
-
|
103
|
-
# Used to merge CLI options with Setuper defaults
|
104
|
-
def reset_options(new_options)
|
105
|
-
@options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
|
106
|
-
end
|
107
|
-
|
108
|
-
# TODO: code smell - too many reset_* methods
|
109
|
-
def reset_evaluator(new_options)
|
110
|
-
@evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
|
111
|
-
end
|
112
|
-
|
113
|
-
def save_scope
|
114
|
-
# This actually replaces scope from command line,
|
115
|
-
# so scope set by 'scope' Pry command will be reset
|
116
|
-
@saved_scope = _prepare_scope(::Guard.scope)
|
117
|
-
end
|
118
|
-
|
119
|
-
def restore_scope
|
120
|
-
::Guard.setup_scope(@saved_scope)
|
121
|
-
end
|
122
|
-
|
123
|
-
attr_reader :watchdirs
|
124
|
-
|
125
|
-
# Stores the scopes defined by the user via the `--group` / `-g` option (to
|
126
|
-
# run only a specific group) or the `--plugin` / `-P` option (to run only a
|
127
|
-
# specific plugin).
|
128
|
-
#
|
129
|
-
# @see CLI#start
|
130
|
-
# @see Dsl#scope
|
131
|
-
#
|
132
|
-
def setup_scope(scope = {})
|
133
|
-
# TODO: there should be a special Scope class instead
|
134
|
-
scope = _prepare_scope(scope)
|
135
|
-
|
136
|
-
::Guard.scope = {
|
137
|
-
groups: scope[:groups].map { |item| ::Guard.add_group(item) },
|
138
|
-
plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
|
139
|
-
}
|
140
|
-
end
|
141
|
-
|
142
|
-
# Evaluates the Guardfile content. It displays an error message if no
|
143
|
-
# Guard plugins are instantiated after the Guardfile evaluation.
|
144
|
-
#
|
145
|
-
# @see Guard::Guardfile::Evaluator#evaluate_guardfile
|
146
|
-
#
|
147
|
-
def evaluate_guardfile
|
148
|
-
evaluator.evaluate_guardfile
|
149
|
-
msg = "No plugins found in Guardfile, please add at least one."
|
150
|
-
::Guard::UI.error msg unless _pluginless_guardfile?
|
151
|
-
end
|
152
|
-
|
153
|
-
# Asynchronously trigger changes
|
154
|
-
#
|
155
|
-
# Currently supported args:
|
156
|
-
#
|
157
|
-
# old style hash: {modified: ['foo'], added: ['bar'], removed: []}
|
158
|
-
#
|
159
|
-
# new style signals with args: [:guard_pause, :unpaused ]
|
160
|
-
#
|
161
|
-
def async_queue_add(changes)
|
162
|
-
@queue << changes
|
163
|
-
|
164
|
-
# Putting interactor in background puts guard into foreground
|
165
|
-
# so it can handle change notifications
|
166
|
-
Thread.new { interactor.background }
|
167
|
-
end
|
168
|
-
|
169
|
-
def pending_changes?
|
170
|
-
! @queue.empty?
|
171
|
-
end
|
172
|
-
|
173
|
-
def add_builtin_plugins(guardfile)
|
174
|
-
return unless guardfile
|
175
|
-
|
176
|
-
pattern = _relative_pathname(guardfile).to_s
|
177
|
-
watcher = ::Guard::Watcher.new(pattern)
|
178
|
-
::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
|
179
|
-
end
|
180
|
-
|
181
|
-
private
|
182
|
-
|
183
|
-
# Initializes the listener and registers a callback for changes.
|
184
|
-
#
|
185
|
-
def _setup_listener
|
186
|
-
if options[:listen_on]
|
187
|
-
Listen.on(options[:listen_on], &_listener_callback)
|
188
|
-
else
|
189
|
-
listener_options = {}
|
190
|
-
[:latency, :force_polling, :wait_for_delay].each do |option|
|
191
|
-
listener_options[option] = options[option] if options[option]
|
192
|
-
end
|
193
|
-
listen_args = watchdirs + [listener_options]
|
194
|
-
Listen.to(*listen_args, &_listener_callback)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# Process the change queue, running tasks within the main Guard thread
|
199
|
-
def _process_queue
|
200
|
-
actions, changes = [], { modified: [], added: [], removed: [] }
|
201
|
-
|
202
|
-
while pending_changes?
|
203
|
-
if (item = @queue.pop).first.is_a?(Symbol)
|
204
|
-
actions << item
|
205
|
-
else
|
206
|
-
item.each { |key, value| changes[key] += value }
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
_run_actions(actions)
|
211
|
-
return if changes.values.all?(&:empty?)
|
212
|
-
runner.run_on_changes(*changes.values)
|
213
|
-
end
|
214
|
-
|
215
|
-
# Sets up traps to catch signals used to control Guard.
|
216
|
-
#
|
217
|
-
# Currently two signals are caught:
|
218
|
-
# - `USR1` which pauses listening to changes.
|
219
|
-
# - `USR2` which resumes listening to changes.
|
220
|
-
# - 'INT' which is delegated to Pry if active, otherwise stops Guard.
|
221
|
-
#
|
222
|
-
def _setup_signal_traps
|
223
|
-
return if defined?(JRUBY_VERSION)
|
224
|
-
|
225
|
-
if Signal.list.keys.include?("USR1")
|
226
|
-
Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
|
227
|
-
end
|
228
|
-
|
229
|
-
if Signal.list.keys.include?("USR2")
|
230
|
-
Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
|
231
|
-
end
|
232
|
-
|
233
|
-
return unless Signal.list.keys.include?("INT")
|
234
|
-
Signal.trap("INT") { interactor.handle_interrupt }
|
235
|
-
end
|
236
|
-
|
237
|
-
# Enables or disables the notifier based on user's configurations.
|
238
|
-
#
|
239
|
-
def _setup_notifier
|
240
|
-
if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
|
241
|
-
::Guard::Notifier.turn_on
|
242
|
-
else
|
243
|
-
::Guard::Notifier.turn_off
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# TODO: Guard::Watch or Guard::Scope should provide this
|
248
|
-
def _scoped_watchers
|
249
|
-
watchers = []
|
250
|
-
runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
|
251
|
-
watchers
|
252
|
-
end
|
253
|
-
|
254
|
-
# Check if any of the changes are actually watched for
|
255
|
-
def _relevant_changes?(changes)
|
256
|
-
files = changes.values.flatten(1)
|
257
|
-
watchers = _scoped_watchers
|
258
|
-
watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
|
259
|
-
end
|
260
|
-
|
261
|
-
def _relative_pathname(path)
|
262
|
-
full_path = Pathname(path)
|
263
|
-
full_path.relative_path_from(Pathname.pwd)
|
264
|
-
rescue ArgumentError
|
265
|
-
full_path
|
266
|
-
end
|
267
|
-
|
268
|
-
def _relative_pathnames(paths)
|
269
|
-
paths.map { |path| _relative_pathname(path) }
|
270
|
-
end
|
271
|
-
|
272
|
-
def _run_actions(actions)
|
273
|
-
actions.each do |action_args|
|
274
|
-
args = action_args.dup
|
275
|
-
namespaced_action = args.shift
|
276
|
-
action = namespaced_action.to_s.sub(/^guard_/, "")
|
277
|
-
if ::Guard.respond_to?(action)
|
278
|
-
::Guard.send(action, *args)
|
279
|
-
else
|
280
|
-
fail "Unknown action: #{action.inspect}"
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
def _setup_watchdirs
|
286
|
-
dirs = Array(options[:watchdir])
|
287
|
-
dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
|
288
|
-
end
|
289
|
-
|
290
|
-
def _listener_callback
|
291
|
-
lambda do |modified, added, removed|
|
292
|
-
relative_paths = {
|
293
|
-
modified: _relative_pathnames(modified),
|
294
|
-
added: _relative_pathnames(added),
|
295
|
-
removed: _relative_pathnames(removed)
|
296
|
-
}
|
297
|
-
|
298
|
-
async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
def _reset_all
|
303
|
-
reset_groups
|
304
|
-
reset_plugins
|
305
|
-
reset_scope
|
306
|
-
end
|
307
|
-
|
308
|
-
def _setup_interactor
|
309
|
-
::Guard::Interactor.new(options[:no_interactions])
|
310
|
-
end
|
311
|
-
|
312
|
-
def _load_guardfile
|
313
|
-
_reset_all
|
314
|
-
evaluate_guardfile
|
315
|
-
setup_scope
|
316
|
-
_setup_notifier
|
317
|
-
end
|
318
|
-
|
319
|
-
def _prepare_scope(scope)
|
320
|
-
fail "Guard::setup() not called!" if options.nil?
|
321
|
-
plugins = Array(options[:plugin])
|
322
|
-
plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
|
323
|
-
|
324
|
-
# Convert objects to names
|
325
|
-
plugins.map! { |p| p.respond_to?(:name) ? p.name : p }
|
326
|
-
|
327
|
-
groups = Array(options[:group])
|
328
|
-
groups = Array(scope[:groups] || scope[:group]) if groups.empty?
|
329
|
-
|
330
|
-
# Convert objects to names
|
331
|
-
groups.map! { |g| g.respond_to?(:name) ? g.name : g }
|
332
|
-
|
333
|
-
{ plugins: plugins, groups: groups }
|
334
|
-
end
|
335
|
-
|
336
|
-
def _pluginless_guardfile?
|
337
|
-
# no Reevaluator means there was no Guardfile configured that could be
|
338
|
-
# reevaluated, so we don't have a pluginless guardfile, because we don't
|
339
|
-
# have a Guardfile to begin with...
|
340
|
-
#
|
341
|
-
# But, if we have a Guardfile, we'll at least have the built-in
|
342
|
-
# Reevaluator, so the following will work:
|
343
|
-
|
344
|
-
plugins.map(&:name) != ["reevaluator"]
|
345
|
-
end
|
346
|
-
|
347
|
-
def _reset_for_tests
|
348
|
-
@options = nil
|
349
|
-
@queue = nil
|
350
|
-
@runner = nil
|
351
|
-
@evaluator = nil
|
352
|
-
@watchdirs = nil
|
353
|
-
@watchdirs = nil
|
354
|
-
@listener = nil
|
355
|
-
@interactor = nil
|
356
|
-
@scope = nil
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|