guard 2.8.2 → 2.9.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 +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
|