guard 0.8.0 → 0.8.1

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.
data/bin/guard CHANGED
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'guard'
4
- require 'guard/cli'
5
-
6
- Guard::CLI.start
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'guard'
4
+ require 'guard/cli'
5
+
6
+ Guard::CLI.start
data/lib/guard.rb CHANGED
@@ -1,384 +1,384 @@
1
- # Guard is the main module for all Guard related modules and classes.
2
- # Also other Guard implementation should use this namespace.
3
- #
4
- module Guard
5
-
6
- autoload :UI, 'guard/ui'
7
- autoload :Dsl, 'guard/dsl'
8
- autoload :DslDescriber, 'guard/dsl_describer'
9
- autoload :Group, 'guard/group'
10
- autoload :Interactor, 'guard/interactor'
11
- autoload :Listener, 'guard/listener'
12
- autoload :Watcher, 'guard/watcher'
13
- autoload :Notifier, 'guard/notifier'
14
- autoload :Hook, 'guard/hook'
15
-
16
- class << self
17
- attr_accessor :options, :interactor, :listener
18
-
19
- # Initialize the Guard singleton.
20
- #
21
- # @option options [Boolean] clear if auto clear the UI should be done
22
- # @option options [Boolean] notify if system notifications should be shown
23
- # @option options [Boolean] debug if debug output should be shown
24
- # @option options [Array<String>] group the list of groups to start
25
- # @option options [String] watchdir the director to watch
26
- # @option options [String] guardfile the path to the Guardfile
27
- # @option options [Boolean] watch_all_modifications watches all file modifications if true
28
- #
29
- def setup(options = {})
30
- @options = options
31
- @guards = []
32
- @groups = [Group.new(:default)]
33
- @interactor = Interactor.new
34
- @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options)
35
-
36
- @options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
37
-
38
- UI.clear if @options[:clear]
39
-
40
- debug_command_execution if @options[:debug]
41
-
42
- self
43
- end
44
-
45
- # Smart accessor for retrieving a specific guard or several guards at once.
46
- #
47
- # @param [String, Symbol] filter return the guard with the given name, or nil if not found
48
- # @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found
49
- # @param [Hash] filter returns all guards matching the given Hash.
50
- # Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found
51
- # @param [NilClass] filter returns all guards
52
- #
53
- # @see Guard.groups
54
- #
55
- def guards(filter = nil)
56
- case filter
57
- when String, Symbol
58
- @guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
59
- when Regexp
60
- @guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter }
61
- when Hash
62
- filter.inject(@guards) do |matches, (k, v)|
63
- if k.to_sym == :name
64
- matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') }
65
- else
66
- matches.find_all { |guard| guard.send(k).to_sym == v.to_sym }
67
- end
68
- end
69
- else
70
- @guards
71
- end
72
- end
73
-
74
- # Smart accessor for retrieving a specific group or several groups at once.
75
- #
76
- # @param [NilClass] filter returns all groups
77
- # @param [String, Symbol] filter return the group with the given name, or nil if not found
78
- # @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found
79
- #
80
- # @see Guard.guards
81
- #
82
- def groups(filter = nil)
83
- case filter
84
- when String, Symbol
85
- @groups.find { |group| group.name == filter.to_sym }
86
- when Regexp
87
- @groups.find_all { |group| group.name.to_s =~ filter }
88
- else
89
- @groups
90
- end
91
- end
92
-
93
- # Start Guard by evaluate the `Guardfile`, initialize the declared Guards
94
- # and start the available file change listener.
95
- #
96
- # @option options [Boolean] clear if auto clear the UI should be done
97
- # @option options [Boolean] notify if system notifications should be shown
98
- # @option options [Boolean] debug if debug output should be shown
99
- # @option options [Array<String>] group the list of groups to start
100
- # @option options [String] watchdir the director to watch
101
- # @option options [String] guardfile the path to the Guardfile
102
- #
103
- def start(options = {})
104
- setup(options)
105
-
106
- Dsl.evaluate_guardfile(options)
107
-
108
- listener.on_change do |files|
109
- Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
110
- listener.changed_files += files if Watcher.match_files?(guards, files)
111
- end
112
-
113
- UI.info "Guard is now watching at '#{ listener.directory }'"
114
-
115
- run_guard_task(:start)
116
-
117
- interactor.start
118
- listener.start
119
- end
120
-
121
- # Stop Guard listening to file changes
122
- #
123
- def stop
124
- UI.info 'Bye bye...', :reset => true
125
- listener.stop
126
- run_guard_task(:stop)
127
- abort
128
- end
129
-
130
- # Reload all Guards currently enabled.
131
- #
132
- def reload
133
- run do
134
- run_guard_task(:reload)
135
- end
136
- end
137
-
138
- # Trigger `run_all` on all Guards currently enabled.
139
- #
140
- def run_all
141
- run do
142
- run_guard_task(:run_all)
143
- end
144
- end
145
-
146
- # Pause Guard listening to file changes.
147
- #
148
- def pause
149
- if listener.locked
150
- UI.info 'Un-paused files modification listening', :reset => true
151
- listener.clear_changed_files
152
- listener.unlock
153
- else
154
- UI.info 'Paused files modification listening', :reset => true
155
- listener.lock
156
- end
157
- end
158
-
159
- # Trigger `run_on_change` on all Guards currently enabled.
160
- #
161
- def run_on_change(paths)
162
- run do
163
- run_guard_task(:run_on_change, paths)
164
- end
165
- end
166
-
167
- # Run a block where the listener and the interactor is
168
- # blocked.
169
- #
170
- # @yield the block to run
171
- #
172
- def run
173
- listener.lock
174
- interactor.lock
175
-
176
- UI.clear if options[:clear]
177
-
178
- begin
179
- yield
180
- rescue Interrupt
181
- end
182
-
183
- interactor.unlock
184
- listener.unlock
185
- end
186
-
187
- # Loop through all groups and run the given task for each Guard.
188
- #
189
- # Stop the task run for the all Guards within a group if one Guard
190
- # throws `:task_has_failed` and the group has its `:halt_on_fail` option to `true`.
191
- #
192
- # @param [Symbol] task the task to run
193
- # @param [Array<String>] files the list of files to pass to the task
194
- #
195
- def run_guard_task(task, files = nil)
196
- groups.each do |group|
197
- catch group.options[:halt_on_fail] == true ? :task_has_failed : :no_catch do
198
- guards(:group => group.name).each do |guard|
199
- if task == :run_on_change
200
- run_on_change_task(files, guard, task)
201
- else
202
- run_supervised_task(guard, task)
203
- end
204
- end
205
- end
206
- end
207
- end
208
-
209
- # Run the `:run_on_change` task. When the option `:watch_all_modifications` is set,
210
- # the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas
211
- # deleted paths run on {Guard::Guard#run_on_deletion}.
212
- #
213
- # @param [Array<String>] files the list of files to pass to the task
214
- # @param [Guard::Guard] guard the guard to run
215
- # @param [Symbol] task the task to run
216
- #
217
- def run_on_change_task(files, guard, task)
218
- paths = Watcher.match_files(guard, files)
219
- changes = changed_paths(paths)
220
- deletions = deleted_paths(paths)
221
-
222
- unless changes.empty?
223
- UI.debug "#{ guard.class.name }##{ task } with #{ changes.inspect }"
224
- run_supervised_task(guard, task, changes)
225
- end
226
-
227
- unless deletions.empty?
228
- UI.debug "#{ guard.class.name }#run_on_deletion with #{ deletions.inspect }"
229
- run_supervised_task(guard, :run_on_deletion, deletions)
230
- end
231
- end
232
-
233
- # Detects the paths that have changed.
234
- #
235
- # Deleted paths are prefixed by an exclamation point.
236
- # @see Guard::Listener#modified_files
237
- #
238
- # @param [Array<String>] paths the watched paths
239
- # @return [Array<String>] the changed paths
240
- #
241
- def changed_paths(paths)
242
- paths.select { |f| !f.start_with?('!') }
243
- end
244
-
245
- # Detects the paths that have been deleted.
246
- #
247
- # Deleted paths are prefixed by an exclamation point.
248
- # @see Guard::Listener#modified_files
249
- #
250
- # @param [Array<String>] paths the watched paths
251
- # @return [Array<String>] the deleted paths
252
- #
253
- def deleted_paths(paths)
254
- paths.select { |f| f.start_with?('!') }.map { |f| f.slice(1..-1) }
255
- end
256
-
257
- # Run a Guard task, but remove the Guard when his work leads to a system failure.
258
- #
259
- # @param [Guard::Guard] guard the Guard to execute
260
- # @param [Symbol] task the task to run
261
- # @param [Array] args the arguments for the task
262
- # @return [Boolean, Exception] the result of the Guard
263
- #
264
- def run_supervised_task(guard, task, *args)
265
- guard.hook("#{ task }_begin", *args)
266
- result = guard.send(task, *args)
267
- guard.hook("#{ task }_end", result)
268
-
269
- result
270
-
271
- rescue Exception => ex
272
- UI.error("#{ guard.class.name } failed to achieve its <#{ task.to_s }>, exception was:" +
273
- "\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
274
-
275
- guards.delete guard
276
- UI.info("\n#{ guard.class.name } has just been fired")
277
-
278
- ex
279
- end
280
-
281
- # Add a Guard to use.
282
- #
283
- # @param [String] name the Guard name
284
- # @param [Array<Watcher>] watchers the list of declared watchers
285
- # @param [Array<Hash>] callbacks the list of callbacks
286
- # @param [Hash] options the Guard options (see the given Guard documentation)
287
- #
288
- def add_guard(name, watchers = [], callbacks = [], options = {})
289
- if name.to_sym == :ego
290
- UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
291
- else
292
- guard_class = get_guard_class(name)
293
- callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
294
- @guards << guard_class.new(watchers, options)
295
- end
296
- end
297
-
298
- # Add a Guard group.
299
- #
300
- # @param [String] name the group name
301
- # @option options [Boolean] halt_on_fail if a task execution
302
- # should be halted for all Guards in this group if one Guard throws `:task_has_failed`
303
- # @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present)
304
- #
305
- def add_group(name, options = {})
306
- group = groups(name)
307
- if group.nil?
308
- group = Group.new(name, options)
309
- @groups << group
310
- end
311
- group
312
- end
313
-
314
- # Tries to load the Guard main class.
315
- #
316
- # @param [String] name the name of the Guard
317
- # @return [Class, nil] the loaded class
318
- #
319
- def get_guard_class(name)
320
- name = name.to_s
321
- try_require = false
322
- const_name = name.downcase.gsub('-', '')
323
- begin
324
- require "guard/#{ name.downcase }" if try_require
325
- self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
326
- rescue TypeError
327
- unless try_require
328
- try_require = true
329
- retry
330
- else
331
- UI.error "Could not find class Guard::#{ const_name.capitalize }"
332
- end
333
- rescue LoadError => loadError
334
- UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
335
- UI.error loadError.to_s
336
- end
337
- end
338
-
339
- # Locate a path to a Guard gem.
340
- #
341
- # @param [String] name the name of the Guard without the prefix `guard-`
342
- # @return [String] the full path to the Guard gem
343
- #
344
- def locate_guard(name)
345
- if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
346
- Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
347
- else
348
- Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
349
- end
350
- rescue
351
- UI.error "Could not find 'guard-#{ name }' gem path."
352
- end
353
-
354
- # Returns a list of guard Gem names installed locally.
355
- #
356
- # @return [Array<String>] a list of guard gem names
357
- #
358
- def guard_gem_names
359
- if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
360
- Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
361
- else
362
- Gem.source_index.find_name(/^guard-/)
363
- end.map { |x| x.name.sub /^guard-/, '' }
364
- end
365
-
366
- # Adds a command logger in debug mode. This wraps common command
367
- # execution functions and logs the executed command before execution.
368
- #
369
- def debug_command_execution
370
- Kernel.send(:alias_method, :original_system, :system)
371
- Kernel.send(:define_method, :system) do |command, *args|
372
- ::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
373
- original_system command, *args
374
- end
375
-
376
- Kernel.send(:alias_method, :original_backtick, :'`')
377
- Kernel.send(:define_method, :'`') do |command|
378
- ::Guard::UI.debug "Command execution: #{ command }"
379
- original_backtick command
380
- end
381
- end
382
-
383
- end
384
- end
1
+ # Guard is the main module for all Guard related modules and classes.
2
+ # Also other Guard implementation should use this namespace.
3
+ #
4
+ module Guard
5
+
6
+ autoload :UI, 'guard/ui'
7
+ autoload :Dsl, 'guard/dsl'
8
+ autoload :DslDescriber, 'guard/dsl_describer'
9
+ autoload :Group, 'guard/group'
10
+ autoload :Interactor, 'guard/interactor'
11
+ autoload :Listener, 'guard/listener'
12
+ autoload :Watcher, 'guard/watcher'
13
+ autoload :Notifier, 'guard/notifier'
14
+ autoload :Hook, 'guard/hook'
15
+
16
+ class << self
17
+ attr_accessor :options, :interactor, :listener
18
+
19
+ # Initialize the Guard singleton.
20
+ #
21
+ # @option options [Boolean] clear if auto clear the UI should be done
22
+ # @option options [Boolean] notify if system notifications should be shown
23
+ # @option options [Boolean] debug if debug output should be shown
24
+ # @option options [Array<String>] group the list of groups to start
25
+ # @option options [String] watchdir the director to watch
26
+ # @option options [String] guardfile the path to the Guardfile
27
+ # @option options [Boolean] watch_all_modifications watches all file modifications if true
28
+ #
29
+ def setup(options = {})
30
+ @options = options
31
+ @guards = []
32
+ @groups = [Group.new(:default)]
33
+ @interactor = Interactor.new
34
+ @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options)
35
+
36
+ @options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
37
+
38
+ UI.clear if @options[:clear]
39
+
40
+ debug_command_execution if @options[:debug]
41
+
42
+ self
43
+ end
44
+
45
+ # Smart accessor for retrieving a specific guard or several guards at once.
46
+ #
47
+ # @param [String, Symbol] filter return the guard with the given name, or nil if not found
48
+ # @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found
49
+ # @param [Hash] filter returns all guards matching the given Hash.
50
+ # Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found
51
+ # @param [NilClass] filter returns all guards
52
+ #
53
+ # @see Guard.groups
54
+ #
55
+ def guards(filter = nil)
56
+ case filter
57
+ when String, Symbol
58
+ @guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
59
+ when Regexp
60
+ @guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter }
61
+ when Hash
62
+ filter.inject(@guards) do |matches, (k, v)|
63
+ if k.to_sym == :name
64
+ matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') }
65
+ else
66
+ matches.find_all { |guard| guard.send(k).to_sym == v.to_sym }
67
+ end
68
+ end
69
+ else
70
+ @guards
71
+ end
72
+ end
73
+
74
+ # Smart accessor for retrieving a specific group or several groups at once.
75
+ #
76
+ # @param [NilClass] filter returns all groups
77
+ # @param [String, Symbol] filter return the group with the given name, or nil if not found
78
+ # @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found
79
+ #
80
+ # @see Guard.guards
81
+ #
82
+ def groups(filter = nil)
83
+ case filter
84
+ when String, Symbol
85
+ @groups.find { |group| group.name == filter.to_sym }
86
+ when Regexp
87
+ @groups.find_all { |group| group.name.to_s =~ filter }
88
+ else
89
+ @groups
90
+ end
91
+ end
92
+
93
+ # Start Guard by evaluate the `Guardfile`, initialize the declared Guards
94
+ # and start the available file change listener.
95
+ #
96
+ # @option options [Boolean] clear if auto clear the UI should be done
97
+ # @option options [Boolean] notify if system notifications should be shown
98
+ # @option options [Boolean] debug if debug output should be shown
99
+ # @option options [Array<String>] group the list of groups to start
100
+ # @option options [String] watchdir the director to watch
101
+ # @option options [String] guardfile the path to the Guardfile
102
+ #
103
+ def start(options = {})
104
+ setup(options)
105
+
106
+ Dsl.evaluate_guardfile(options)
107
+
108
+ listener.on_change do |files|
109
+ Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
110
+ listener.changed_files += files if Watcher.match_files?(guards, files)
111
+ end
112
+
113
+ UI.info "Guard is now watching at '#{ listener.directory }'"
114
+
115
+ run_guard_task(:start)
116
+
117
+ interactor.start
118
+ listener.start
119
+ end
120
+
121
+ # Stop Guard listening to file changes
122
+ #
123
+ def stop
124
+ UI.info 'Bye bye...', :reset => true
125
+ listener.stop
126
+ run_guard_task(:stop)
127
+ abort
128
+ end
129
+
130
+ # Reload all Guards currently enabled.
131
+ #
132
+ def reload
133
+ run do
134
+ run_guard_task(:reload)
135
+ end
136
+ end
137
+
138
+ # Trigger `run_all` on all Guards currently enabled.
139
+ #
140
+ def run_all
141
+ run do
142
+ run_guard_task(:run_all)
143
+ end
144
+ end
145
+
146
+ # Pause Guard listening to file changes.
147
+ #
148
+ def pause
149
+ if listener.locked
150
+ UI.info 'Un-paused files modification listening', :reset => true
151
+ listener.clear_changed_files
152
+ listener.unlock
153
+ else
154
+ UI.info 'Paused files modification listening', :reset => true
155
+ listener.lock
156
+ end
157
+ end
158
+
159
+ # Trigger `run_on_change` on all Guards currently enabled.
160
+ #
161
+ def run_on_change(paths)
162
+ run do
163
+ run_guard_task(:run_on_change, paths)
164
+ end
165
+ end
166
+
167
+ # Run a block where the listener and the interactor is
168
+ # blocked.
169
+ #
170
+ # @yield the block to run
171
+ #
172
+ def run
173
+ listener.lock
174
+ interactor.lock
175
+
176
+ UI.clear if options[:clear]
177
+
178
+ begin
179
+ yield
180
+ rescue Interrupt
181
+ end
182
+
183
+ interactor.unlock
184
+ listener.unlock
185
+ end
186
+
187
+ # Loop through all groups and run the given task for each Guard.
188
+ #
189
+ # Stop the task run for the all Guards within a group if one Guard
190
+ # throws `:task_has_failed` and the group has its `:halt_on_fail` option to `true`.
191
+ #
192
+ # @param [Symbol] task the task to run
193
+ # @param [Array<String>] files the list of files to pass to the task
194
+ #
195
+ def run_guard_task(task, files = nil)
196
+ groups.each do |group|
197
+ catch group.options[:halt_on_fail] == true ? :task_has_failed : :no_catch do
198
+ guards(:group => group.name).each do |guard|
199
+ if task == :run_on_change
200
+ run_on_change_task(files, guard, task)
201
+ else
202
+ run_supervised_task(guard, task)
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ # Run the `:run_on_change` task. When the option `:watch_all_modifications` is set,
210
+ # the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas
211
+ # deleted paths run on {Guard::Guard#run_on_deletion}.
212
+ #
213
+ # @param [Array<String>] files the list of files to pass to the task
214
+ # @param [Guard::Guard] guard the guard to run
215
+ # @param [Symbol] task the task to run
216
+ #
217
+ def run_on_change_task(files, guard, task)
218
+ paths = Watcher.match_files(guard, files)
219
+ changes = changed_paths(paths)
220
+ deletions = deleted_paths(paths)
221
+
222
+ unless changes.empty?
223
+ UI.debug "#{ guard.class.name }##{ task } with #{ changes.inspect }"
224
+ run_supervised_task(guard, task, changes)
225
+ end
226
+
227
+ unless deletions.empty?
228
+ UI.debug "#{ guard.class.name }#run_on_deletion with #{ deletions.inspect }"
229
+ run_supervised_task(guard, :run_on_deletion, deletions)
230
+ end
231
+ end
232
+
233
+ # Detects the paths that have changed.
234
+ #
235
+ # Deleted paths are prefixed by an exclamation point.
236
+ # @see Guard::Listener#modified_files
237
+ #
238
+ # @param [Array<String>] paths the watched paths
239
+ # @return [Array<String>] the changed paths
240
+ #
241
+ def changed_paths(paths)
242
+ paths.select { |f| !f.start_with?('!') }
243
+ end
244
+
245
+ # Detects the paths that have been deleted.
246
+ #
247
+ # Deleted paths are prefixed by an exclamation point.
248
+ # @see Guard::Listener#modified_files
249
+ #
250
+ # @param [Array<String>] paths the watched paths
251
+ # @return [Array<String>] the deleted paths
252
+ #
253
+ def deleted_paths(paths)
254
+ paths.select { |f| f.start_with?('!') }.map { |f| f.slice(1..-1) }
255
+ end
256
+
257
+ # Run a Guard task, but remove the Guard when his work leads to a system failure.
258
+ #
259
+ # @param [Guard::Guard] guard the Guard to execute
260
+ # @param [Symbol] task the task to run
261
+ # @param [Array] args the arguments for the task
262
+ # @return [Boolean, Exception] the result of the Guard
263
+ #
264
+ def run_supervised_task(guard, task, *args)
265
+ guard.hook("#{ task }_begin", *args)
266
+ result = guard.send(task, *args)
267
+ guard.hook("#{ task }_end", result)
268
+
269
+ result
270
+
271
+ rescue Exception => ex
272
+ UI.error("#{ guard.class.name } failed to achieve its <#{ task.to_s }>, exception was:" +
273
+ "\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
274
+
275
+ guards.delete guard
276
+ UI.info("\n#{ guard.class.name } has just been fired")
277
+
278
+ ex
279
+ end
280
+
281
+ # Add a Guard to use.
282
+ #
283
+ # @param [String] name the Guard name
284
+ # @param [Array<Watcher>] watchers the list of declared watchers
285
+ # @param [Array<Hash>] callbacks the list of callbacks
286
+ # @param [Hash] options the Guard options (see the given Guard documentation)
287
+ #
288
+ def add_guard(name, watchers = [], callbacks = [], options = {})
289
+ if name.to_sym == :ego
290
+ UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
291
+ else
292
+ guard_class = get_guard_class(name)
293
+ callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
294
+ @guards << guard_class.new(watchers, options)
295
+ end
296
+ end
297
+
298
+ # Add a Guard group.
299
+ #
300
+ # @param [String] name the group name
301
+ # @option options [Boolean] halt_on_fail if a task execution
302
+ # should be halted for all Guards in this group if one Guard throws `:task_has_failed`
303
+ # @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present)
304
+ #
305
+ def add_group(name, options = {})
306
+ group = groups(name)
307
+ if group.nil?
308
+ group = Group.new(name, options)
309
+ @groups << group
310
+ end
311
+ group
312
+ end
313
+
314
+ # Tries to load the Guard main class.
315
+ #
316
+ # @param [String] name the name of the Guard
317
+ # @return [Class, nil] the loaded class
318
+ #
319
+ def get_guard_class(name)
320
+ name = name.to_s
321
+ try_require = false
322
+ const_name = name.downcase.gsub('-', '')
323
+ begin
324
+ require "guard/#{ name.downcase }" if try_require
325
+ self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
326
+ rescue TypeError
327
+ unless try_require
328
+ try_require = true
329
+ retry
330
+ else
331
+ UI.error "Could not find class Guard::#{ const_name.capitalize }"
332
+ end
333
+ rescue LoadError => loadError
334
+ UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
335
+ UI.error loadError.to_s
336
+ end
337
+ end
338
+
339
+ # Locate a path to a Guard gem.
340
+ #
341
+ # @param [String] name the name of the Guard without the prefix `guard-`
342
+ # @return [String] the full path to the Guard gem
343
+ #
344
+ def locate_guard(name)
345
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
346
+ Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
347
+ else
348
+ Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
349
+ end
350
+ rescue
351
+ UI.error "Could not find 'guard-#{ name }' gem path."
352
+ end
353
+
354
+ # Returns a list of guard Gem names installed locally.
355
+ #
356
+ # @return [Array<String>] a list of guard gem names
357
+ #
358
+ def guard_gem_names
359
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
360
+ Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
361
+ else
362
+ Gem.source_index.find_name(/^guard-/)
363
+ end.map { |x| x.name.sub /^guard-/, '' }
364
+ end
365
+
366
+ # Adds a command logger in debug mode. This wraps common command
367
+ # execution functions and logs the executed command before execution.
368
+ #
369
+ def debug_command_execution
370
+ Kernel.send(:alias_method, :original_system, :system)
371
+ Kernel.send(:define_method, :system) do |command, *args|
372
+ ::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
373
+ original_system command, *args
374
+ end
375
+
376
+ Kernel.send(:alias_method, :original_backtick, :'`')
377
+ Kernel.send(:define_method, :'`') do |command|
378
+ ::Guard::UI.debug "Command execution: #{ command }"
379
+ original_backtick command
380
+ end
381
+ end
382
+
383
+ end
384
+ end